mirror of
https://github.com/coder/coder.git
synced 2026-10-30 18:12:29 +08:00
Adds group and user ACLs to org-scoped MCP server configs so
organizations can restrict specific MCP servers to subsets of members,
mirroring the template ACL pattern.
## Summary
- Migration adds `group_acl`/`user_acl` JSONB columns (nested `{"<id>":
{"permissions": [...]}}` shape) and seeds every existing config with its
organization's Everyone group read entry (the Everyone group ID equals
the org ID), so member access is unchanged by default. Creation seeds
the same entry.
- The blanket org-member read grant from the base PR is replaced by ACL
evaluation: Rego requires org membership for every ACL grant, and
`GetAuthorizedMCPServerConfigs` compiles ACL-aware SQL filters.
- New `ActionShare` (org admins) gates `GET|PATCH
/api/experimental/organizations/{organization}/mcp-servers/{mcpserverconfig}/acl`
(nested under the organization like the rest of the config surface).
PATCH validates principals against the config's organization, merges
sparse updates under a row lock, stamps `updated_by`/`updated_at`, and
is audited as a Write with `Old` captured before authorization. A config
deleted concurrently between the middleware fetch and the locked
re-fetch is concealed as 404, matching the update and delete handlers.
ACL columns are tracked in the audit table.
- ACL management is available in all editions (no enterprise
entitlement), documented in the MCP servers page. Revoking an ACL does
not retro-strip already-selected configs from existing chats; new
selection is blocked at chat create.
- Force On respects the ACL: the forced set is loaded as the chat owner,
so a `force_on` server whose ACL denies the owner never attaches at
create, send, or generation time.
- No rolling-upgrade machinery: upgrades run in scheduled maintenance
downtime, so the migration only backfills existing rows; the API sets
the Everyone read grant explicitly on every insert.
Stacked on #27943. Part of the MCP org-separation stack.
Closes https://linear.app/codercom/issue/CODAGT-712
UAT: verified on a dogfood instance with two members and a custom group:
Everyone-seed default visibility, group grant with Everyone removal
(non-member loses list/fetch/selection), foreign-principal rejection,
non-admin share denial (audited 403 / concealed 404), user_acl restore,
and audited ACL diffs.
> Mux (AI agent) authored this PR on Mike's behalf.
<!-- mux-attribution: model=claude-fable-5 thinking=high -->
1798 lines
68 KiB
Go
1798 lines
68 KiB
Go
package rbac_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/coderd/rbac/policy"
|
|
)
|
|
|
|
type hasAuthSubjects interface {
|
|
Subjects() []authSubject
|
|
}
|
|
|
|
type authSubjectSet []authSubject
|
|
|
|
func (a authSubjectSet) Subjects() []authSubject { return a }
|
|
|
|
type authSubject struct {
|
|
// Name is helpful for test assertions
|
|
Name string
|
|
Actor rbac.Subject
|
|
}
|
|
|
|
func (a authSubject) Subjects() []authSubject { return []authSubject{a} }
|
|
|
|
// TestBuiltInRoles makes sure our built-in roles are valid by our own policy
|
|
// rules. If this is incorrect, that is a mistake.
|
|
func TestBuiltInRoles(t *testing.T) {
|
|
t.Parallel()
|
|
for _, r := range rbac.SiteBuiltInRoles() {
|
|
t.Run(r.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
require.NoError(t, r.Valid(), "invalid role")
|
|
})
|
|
}
|
|
|
|
for _, r := range rbac.OrganizationRoles(uuid.New()) {
|
|
t.Run(r.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
require.NoError(t, r.Valid(), "invalid role")
|
|
})
|
|
}
|
|
}
|
|
|
|
// permissionGranted checks whether a permission list contains a
|
|
// matching entry for the target, accounting for wildcard actions.
|
|
// It does not evaluate negations that may override a positive grant.
|
|
func permissionGranted(perms []rbac.Permission, target rbac.Permission) bool {
|
|
return slices.ContainsFunc(perms, func(p rbac.Permission) bool {
|
|
return p.Negate == target.Negate &&
|
|
p.ResourceType == target.ResourceType &&
|
|
(p.Action == target.Action || p.Action == policy.WildcardSymbol)
|
|
})
|
|
}
|
|
|
|
func TestOrgSharingPermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
permsFunc func(rbac.OrgSettings) rbac.OrgRolePermissions
|
|
mode rbac.ShareableWorkspaceOwners
|
|
orgReadMembers bool
|
|
orgReadGroups bool
|
|
orgNegateShare bool
|
|
memberNegateShare bool
|
|
}{
|
|
{"Member/Everyone", rbac.OrgMemberPermissions, rbac.ShareableWorkspaceOwnersEveryone, true, true, false, false},
|
|
{"Member/None", rbac.OrgMemberPermissions, rbac.ShareableWorkspaceOwnersNone, false, false, true, true},
|
|
{"Member/ServiceAccounts", rbac.OrgMemberPermissions, rbac.ShareableWorkspaceOwnersServiceAccounts, true, false, false, true},
|
|
{"ServiceAccount/Everyone", rbac.OrgServiceAccountPermissions, rbac.ShareableWorkspaceOwnersEveryone, true, true, false, false},
|
|
{"ServiceAccount/None", rbac.OrgServiceAccountPermissions, rbac.ShareableWorkspaceOwnersNone, false, false, true, false},
|
|
{"ServiceAccount/ServiceAccounts", rbac.OrgServiceAccountPermissions, rbac.ShareableWorkspaceOwnersServiceAccounts, true, true, false, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
perms := tt.permsFunc(rbac.OrgSettings{
|
|
ShareableWorkspaceOwners: tt.mode,
|
|
})
|
|
|
|
assert.Equal(t, tt.orgReadMembers, permissionGranted(perms.Org, rbac.Permission{
|
|
ResourceType: rbac.ResourceOrganizationMember.Type,
|
|
Action: policy.ActionRead,
|
|
}), "org read members")
|
|
|
|
assert.Equal(t, tt.orgReadGroups, permissionGranted(perms.Org, rbac.Permission{
|
|
ResourceType: rbac.ResourceGroup.Type,
|
|
Action: policy.ActionRead,
|
|
}), "org read groups")
|
|
|
|
assert.Equal(t, tt.orgNegateShare, permissionGranted(perms.Org, rbac.Permission{
|
|
Negate: true,
|
|
ResourceType: rbac.ResourceWorkspace.Type,
|
|
Action: policy.ActionShare,
|
|
}), "org negate share")
|
|
|
|
assert.Equal(t, tt.memberNegateShare, permissionGranted(perms.Member, rbac.Permission{
|
|
Negate: true,
|
|
ResourceType: rbac.ResourceWorkspace.Type,
|
|
Action: policy.ActionShare,
|
|
}), "member negate share")
|
|
})
|
|
}
|
|
}
|
|
|
|
//nolint:tparallel,paralleltest
|
|
func TestChatSharingPermissions(t *testing.T) {
|
|
target := rbac.Permission{
|
|
Negate: true,
|
|
ResourceType: rbac.ResourceChat.Type,
|
|
Action: policy.ActionShare,
|
|
}
|
|
orgID := uuid.New()
|
|
userID := uuid.NewString()
|
|
resource := rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(userID)
|
|
|
|
authorizeAgentsAccessUser := func(t *testing.T) error {
|
|
t.Helper()
|
|
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
agentsRole, err := rbac.RoleByName(rbac.ScopedRoleAgentsAccess(orgID))
|
|
require.NoError(t, err)
|
|
|
|
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
|
return auth.Authorize(context.Background(), rbac.Subject{
|
|
ID: userID,
|
|
Roles: rbac.Roles{memberRole, agentsRole},
|
|
Scope: rbac.ScopeAll,
|
|
}, policy.ActionShare, resource)
|
|
}
|
|
|
|
t.Run("Default", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(nil)
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
assert.False(t, permissionGranted(memberRole.Site, target))
|
|
require.NoError(t, authorizeAgentsAccessUser(t))
|
|
})
|
|
|
|
t.Run("Disabled", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(&rbac.RoleOptions{
|
|
NoChatSharing: true,
|
|
})
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
assert.True(t, permissionGranted(memberRole.Site, target))
|
|
|
|
err = authorizeAgentsAccessUser(t)
|
|
require.ErrorAs(t, err, &rbac.UnauthorizedError{})
|
|
})
|
|
}
|
|
|
|
//nolint:tparallel,paralleltest
|
|
func TestOwnerExec(t *testing.T) {
|
|
owner := rbac.Subject{
|
|
ID: uuid.NewString(),
|
|
Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()},
|
|
Scope: rbac.ScopeAll,
|
|
}
|
|
|
|
t.Run("NoExec", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(&rbac.RoleOptions{
|
|
NoOwnerWorkspaceExec: true,
|
|
})
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
// Exec a random workspace
|
|
err := auth.Authorize(context.Background(), owner, policy.ActionSSH,
|
|
rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
|
require.ErrorAsf(t, err, &rbac.UnauthorizedError{}, "expected unauthorized error")
|
|
})
|
|
|
|
t.Run("Exec", func(t *testing.T) {
|
|
rbac.ReloadBuiltinRoles(&rbac.RoleOptions{
|
|
NoOwnerWorkspaceExec: false,
|
|
})
|
|
t.Cleanup(func() { rbac.ReloadBuiltinRoles(nil) })
|
|
|
|
auth := rbac.NewStrictCachingAuthorizer(prometheus.NewRegistry())
|
|
|
|
// Exec a random workspace
|
|
err := auth.Authorize(context.Background(), owner, policy.ActionSSH,
|
|
rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(uuid.New()).WithOwner(uuid.NewString()))
|
|
require.NoError(t, err, "expected owner can")
|
|
})
|
|
}
|
|
|
|
// TestMemberRolesExcludeWorkspacePerms verifies that organization-member
|
|
// and organization-service-account grant no workspace permissions, and
|
|
// that the registered organization-workspace-access role is what carries
|
|
// them.
|
|
//
|
|
// Reads the global builtin role registry via RoleByName, which sibling
|
|
// tests reload, so it must run serially.
|
|
//
|
|
//nolint:paralleltest
|
|
func TestMemberRolesExcludeWorkspacePerms(t *testing.T) {
|
|
orgSettings := rbac.OrgSettings{
|
|
ShareableWorkspaceOwners: rbac.ShareableWorkspaceOwnersEveryone,
|
|
}
|
|
|
|
hasResource := func(perms []rbac.Permission, resource string) bool {
|
|
return slices.ContainsFunc(perms, func(p rbac.Permission) bool {
|
|
return p.ResourceType == resource && !p.Negate
|
|
})
|
|
}
|
|
|
|
member := rbac.OrgMemberPermissions(orgSettings).Member
|
|
require.False(t, hasResource(member, rbac.ResourceWorkspace.Type), "organization-member must not grant workspace permissions")
|
|
require.True(t, hasResource(member, rbac.ResourceOrganizationMember.Type), "organization-member should grant read-self")
|
|
|
|
sa := rbac.OrgServiceAccountPermissions(orgSettings).Member
|
|
require.False(t, hasResource(sa, rbac.ResourceWorkspace.Type), "organization-service-account must not grant workspace permissions")
|
|
require.True(t, hasResource(sa, rbac.ResourceOrganizationMember.Type), "organization-service-account should grant read-self")
|
|
|
|
// The registered organization-workspace-access role is the grant
|
|
// path for workspace permissions.
|
|
orgID := uuid.New()
|
|
wsAccess, err := rbac.RoleByName(rbac.ScopedRoleOrgWorkspaceAccess(orgID))
|
|
require.NoError(t, err)
|
|
require.True(t, hasResource(wsAccess.ByOrgID[orgID.String()].Member, rbac.ResourceWorkspace.Type),
|
|
"organization-workspace-access should grant workspace permissions")
|
|
}
|
|
|
|
// These were "pared down" in https://github.com/coder/coder/pull/21359 to avoid
|
|
// using the now DB-backed organization-member role. As a result, they no longer
|
|
// model real-world org-scoped users (who also have organization-member).
|
|
//
|
|
// For example, `org_auditor` is now expected to be forbidden for
|
|
// `assign_org_role:read`, even though in production an org auditor can read
|
|
// available org roles via the org-member baseline.
|
|
//
|
|
// The tests are still useful for unit-testing the built-in roles in isolation.
|
|
//
|
|
// TODO(geokat): Add an integration test that includes organization-member to
|
|
// recover the old test coverage.
|
|
//
|
|
// nolint:tparallel,paralleltest // subtests share a map, just run sequentially.
|
|
func TestRolePermissions(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
crud := []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
|
|
|
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
|
|
|
// currentUser is anything that references "me", "mine", or "my".
|
|
currentUser := uuid.New()
|
|
adminID := uuid.New()
|
|
templateAdminID := uuid.New()
|
|
userAdminID := uuid.New()
|
|
auditorID := uuid.New()
|
|
orgID := uuid.New()
|
|
otherOrg := uuid.New()
|
|
workspaceID := uuid.New()
|
|
templateID := uuid.New()
|
|
fileID := uuid.New()
|
|
groupID := uuid.New()
|
|
apiKeyID := uuid.New()
|
|
|
|
// Subjects to user
|
|
memberMe := authSubject{Name: "member_me", Actor: rbac.Subject{ID: currentUser.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember()}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
|
|
owner := authSubject{Name: "owner", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleOwner()}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
templateAdmin := authSubject{Name: "template-admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleTemplateAdmin()}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
userAdmin := authSubject{Name: "user-admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleUserAdmin()}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
auditor := authSubject{Name: "auditor", Actor: rbac.Subject{ID: auditorID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.RoleAuditor()}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
|
|
orgAdmin := authSubject{Name: "org_admin", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
orgAuditor := authSubject{Name: "org_auditor", Actor: rbac.Subject{ID: auditorID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAuditor(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
orgUserAdmin := authSubject{Name: "org_user_admin", Actor: rbac.Subject{ID: templateAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgUserAdmin(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
orgTemplateAdmin := authSubject{Name: "org_template_admin", Actor: rbac.Subject{ID: userAdminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgTemplateAdmin(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
orgAdminBanWorkspace := authSubject{Name: "org_admin_workspace_ban", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(orgID), rbac.ScopedRoleOrgWorkspaceCreationBan(orgID)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
agentsAccessUser := func() authSubject {
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
agentsRole, err := rbac.RoleByName(rbac.ScopedRoleAgentsAccess(orgID))
|
|
require.NoError(t, err)
|
|
return authSubject{
|
|
Name: "agents_access",
|
|
Actor: rbac.Subject{
|
|
ID: currentUser.String(),
|
|
Roles: rbac.Roles{memberRole, agentsRole},
|
|
Scope: rbac.ScopeAll,
|
|
}.WithCachedASTValue(),
|
|
}
|
|
}()
|
|
|
|
orgWorkspaceAccessUser := func() authSubject {
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
orgWorkspaceAccessRole, err := rbac.RoleByName(rbac.ScopedRoleOrgWorkspaceAccess(orgID))
|
|
require.NoError(t, err)
|
|
return authSubject{
|
|
Name: "org_workspace_access",
|
|
Actor: rbac.Subject{
|
|
ID: currentUser.String(),
|
|
Roles: rbac.Roles{memberRole, orgWorkspaceAccessRole},
|
|
Scope: rbac.ScopeAll,
|
|
}.WithCachedASTValue(),
|
|
}
|
|
}()
|
|
|
|
orgMemberMe := func() authSubject {
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
perms := rbac.OrgMemberPermissions(rbac.OrgSettings{
|
|
ShareableWorkspaceOwners: rbac.ShareableWorkspaceOwnersEveryone,
|
|
})
|
|
return authSubject{
|
|
Name: "org_member_me",
|
|
Actor: rbac.Subject{
|
|
ID: currentUser.String(),
|
|
Roles: rbac.Roles{
|
|
memberRole,
|
|
{
|
|
Identifier: rbac.ScopedRoleOrgMember(orgID),
|
|
Site: []rbac.Permission{},
|
|
User: []rbac.Permission{},
|
|
ByOrgID: map[string]rbac.OrgPermissions{
|
|
orgID.String(): {
|
|
Org: perms.Org,
|
|
Member: perms.Member,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Scope: rbac.ScopeAll,
|
|
}.WithCachedASTValue(),
|
|
}
|
|
}()
|
|
setOrgNotMe := authSubjectSet{orgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin}
|
|
|
|
otherOrgAdmin := authSubject{Name: "org_admin_other", Actor: rbac.Subject{ID: uuid.NewString(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAdmin(otherOrg)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
otherOrgAuditor := authSubject{Name: "org_auditor_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgAuditor(otherOrg)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
otherOrgUserAdmin := authSubject{Name: "org_user_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgUserAdmin(otherOrg)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
otherOrgTemplateAdmin := authSubject{Name: "org_template_admin_other", Actor: rbac.Subject{ID: adminID.String(), Roles: rbac.RoleIdentifiers{rbac.RoleMember(), rbac.ScopedRoleOrgTemplateAdmin(otherOrg)}, Scope: rbac.ScopeAll}.WithCachedASTValue()}
|
|
setOtherOrg := authSubjectSet{otherOrgAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin}
|
|
|
|
// requiredSubjects are required to be asserted in each test case. This is
|
|
// to make sure one is not forgotten.
|
|
requiredSubjects := []authSubject{
|
|
memberMe, owner, agentsAccessUser, orgWorkspaceAccessUser,
|
|
orgAdmin, otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
}
|
|
|
|
testCases := []struct {
|
|
// Name the test case to better locate the failing test case.
|
|
Name string
|
|
Resource rbac.Object
|
|
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
|
|
// "false".
|
|
// true: Subjects who Authorize should return no error
|
|
// false: Subjects who Authorize should return forbidden.
|
|
AuthorizeMap map[bool][]hasAuthSubjects
|
|
}{
|
|
{
|
|
Name: "MyUser",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceUserObject(currentUser),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgUserAdmin, otherOrgAdmin, otherOrgUserAdmin, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {
|
|
orgTemplateAdmin, orgAuditor,
|
|
otherOrgAuditor, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "AUser",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceUser,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "UpdateMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionUpdate},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateDeleteMyWorkspaceInOrg",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionDelete},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateWorkspaceForMembers",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceWorkspace.InOrg(orgID).WithOwner(policy.WildcardSymbol),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyWorkspaceInOrgExecution",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionSSH},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyWorkspaceInOrgAppConnect",
|
|
// When creating the WithID won't be set, but it does not change the result.
|
|
Actions: []policy.Action{policy.ActionApplicationConnect},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateDeleteWorkspaceAgent",
|
|
Actions: []policy.Action{policy.ActionCreateAgent, policy.ActionDeleteAgent},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgAdminBanWorkspace},
|
|
},
|
|
},
|
|
{
|
|
Name: "UpdateWorkspaceAgent",
|
|
Actions: []policy.Action{policy.ActionUpdateAgent},
|
|
Resource: rbac.ResourceWorkspace.WithID(workspaceID).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "ShareMyWorkspace",
|
|
Actions: []policy.Action{policy.ActionShare},
|
|
Resource: rbac.ResourceWorkspace.
|
|
WithID(workspaceID).
|
|
InOrg(orgID).
|
|
WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgAdminBanWorkspace, orgWorkspaceAccessUser},
|
|
false: {
|
|
memberMe, agentsAccessUser, setOtherOrg,
|
|
templateAdmin, userAdmin,
|
|
orgTemplateAdmin, orgUserAdmin, orgAuditor,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ShareWorkspaceDormant",
|
|
Actions: []policy.Action{policy.ActionShare},
|
|
Resource: rbac.ResourceWorkspaceDormant.
|
|
WithID(uuid.New()).
|
|
InOrg(orgID).
|
|
WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {
|
|
orgAdmin, owner, setOtherOrg,
|
|
userAdmin, memberMe, agentsAccessUser,
|
|
templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor,
|
|
orgAdminBanWorkspace,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "Templates",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadTemplates",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionViewInsights},
|
|
Resource: rbac.ResourceTemplate.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAuditor, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "UseTemplates",
|
|
Actions: []policy.Action{policy.ActionUse},
|
|
Resource: rbac.ResourceTemplate.InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {policy.ActionUse},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Files",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceFile.WithID(fileID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin},
|
|
// Org template admins can only read org scoped files.
|
|
// File scope is currently not org scoped :cry:
|
|
false: {setOtherOrg, orgTemplateAdmin, orgAdmin, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "MyFile",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead},
|
|
Resource: rbac.ResourceFile.WithID(fileID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, setOrgNotMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateOrganizations",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceOrganization,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Organizations",
|
|
Actions: []policy.Action{policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgTemplateAdmin, orgUserAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrganizations",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceOrganization.WithID(orgID).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin, auditor, orgAuditor, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateUpdateDeleteCustomRole",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceAssignOrgRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, userAdmin, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "RoleAssignment",
|
|
Actions: []policy.Action{policy.ActionAssign, policy.ActionUnassign},
|
|
Resource: rbac.ResourceAssignRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceAssignRole,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {setOtherOrg, setOrgNotMe, owner, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
Name: "OrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionAssign, policy.ActionUnassign},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateOrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrgRoleAssignment",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceAssignOrgRole.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgUserAdmin, userAdmin, templateAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, orgAuditor, orgTemplateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "APIKey",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete, policy.ActionUpdate},
|
|
Resource: rbac.ResourceApiKey.WithID(apiKeyID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, setOrgNotMe, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "InboxNotification",
|
|
Actions: []policy.Action{
|
|
policy.ActionCreate, policy.ActionRead, policy.ActionUpdate,
|
|
},
|
|
Resource: rbac.ResourceInboxNotification.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgUserAdmin, orgTemplateAdmin, orgAuditor, templateAdmin, userAdmin, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserData",
|
|
Actions: []policy.Action{policy.ActionReadPersonal, policy.ActionUpdatePersonal},
|
|
Resource: rbac.ResourceUserObject(currentUser),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, setOrgNotMe, templateAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "ManageOrgMember",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, orgTemplateAdmin, orgAuditor, memberMe, agentsAccessUser, templateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ReadOrgMember",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceOrganizationMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin},
|
|
false: {memberMe, agentsAccessUser, setOtherOrg, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "AllUsersGroupACL",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceTemplate.WithID(templateID).InOrg(orgID).WithGroupACL(
|
|
map[string][]policy.Action{
|
|
orgID.String(): {policy.ActionRead},
|
|
}),
|
|
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgUserAdmin, orgTemplateAdmin, orgAuditor, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "Groups",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionDelete, policy.ActionUpdate},
|
|
Resource: rbac.ResourceGroup.WithID(groupID).InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {
|
|
policy.ActionRead,
|
|
},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupsRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceGroup.WithID(groupID).InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
groupID.String(): {
|
|
policy.ActionRead,
|
|
},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupMemberMeRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceGroupMember.WithID(currentUser).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "GroupMemberOtherRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceGroupMember.WithID(adminID).InOrg(orgID).WithOwner(adminID.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAuditor, orgAdmin, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceDormantRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgAdmin, owner, templateAdmin, orgTemplateAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceDormant",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionWorkspaceStop, policy.ActionCreateAgent, policy.ActionDeleteAgent, policy.ActionUpdateAgent},
|
|
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgAdmin, owner, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceDormantUse",
|
|
Actions: []policy.Action{policy.ActionWorkspaceStart, policy.ActionApplicationConnect, policy.ActionSSH},
|
|
Resource: rbac.ResourceWorkspaceDormant.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, userAdmin, owner, templateAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceBuild",
|
|
Actions: []policy.Action{policy.ActionWorkspaceStart, policy.ActionWorkspaceStop},
|
|
Resource: rbac.ResourceWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, userAdmin, templateAdmin, memberMe, agentsAccessUser, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "PrebuiltWorkspace",
|
|
Actions: []policy.Action{policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourcePrebuiltWorkspace.WithID(uuid.New()).InOrg(orgID).WithOwner(database.PrebuildsSystemUserID.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, templateAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, userAdmin, memberMe, agentsAccessUser, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Task",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceTask.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, userAdmin, templateAdmin, memberMe, agentsAccessUser, orgTemplateAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
// Some admin style resources
|
|
{
|
|
Name: "Licenses",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceLicense,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "DeploymentStats",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceDeploymentStats,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "DeploymentConfig",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceDeploymentConfig,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "MCPServerConfigRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceMCPServerConfig.WithID(uuid.New()).InOrg(orgID).WithGroupACL(map[string][]policy.Action{
|
|
orgID.String(): {policy.ActionRead},
|
|
}),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgAuditor, orgMemberMe, agentsAccessUser, orgWorkspaceAccessUser, orgUserAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, memberMe, templateAdmin, userAdmin, auditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "MCPServerConfigWrite",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionShare},
|
|
Resource: rbac.ResourceMCPServerConfig.WithID(uuid.New()).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, orgAuditor, auditor, orgMemberMe, memberMe, agentsAccessUser, orgWorkspaceAccessUser, orgUserAdmin, orgTemplateAdmin, templateAdmin, userAdmin},
|
|
},
|
|
},
|
|
{
|
|
Name: "DebugInfo",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceDebugInfo,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Replicas",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceReplicas,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "TailnetCoordinator",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceTailnetCoordinator,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "AuditLogs",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionCreate},
|
|
Resource: rbac.ResourceAuditLog,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerDaemons",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, agentsAccessUser, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerDaemonsRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceProvisionerDaemon.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgAdmin, orgTemplateAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgAuditor, orgUserAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserProvisionerDaemonsCreate",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceProvisionerDaemon.WithOwner(currentUser.String()).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgTemplateAdmin, orgAdmin, orgWorkspaceAccessUser},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserProvisionerDaemonsUpdateDelete",
|
|
Actions: []policy.Action{policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceProvisionerDaemon.WithOwner(currentUser.String()).InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgTemplateAdmin, orgAdmin},
|
|
false: {orgWorkspaceAccessUser, setOtherOrg, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, orgAuditor},
|
|
},
|
|
},
|
|
{
|
|
Name: "ProvisionerJobs",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionCreate},
|
|
Resource: rbac.ResourceProvisionerJobs.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgTemplateAdmin, orgAdmin},
|
|
false: {setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "System",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceSystem,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2App",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceOauth2App,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2AppRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceOauth2App,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2AppSecret",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceOauth2AppSecret,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "Oauth2Token",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceOauth2AppCodeToken,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceProxy",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceWorkspaceProxy,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceProxyRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceWorkspaceProxy,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
false: {},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceBuildOrchestration",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceWorkspaceBuildOrchestration.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOrgNotMe, setOtherOrg, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
// Any owner/admin across may access any users' preferences
|
|
// Members may not access other members' preferences
|
|
Name: "NotificationPreferencesOwn",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationPreference.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgWorkspaceAccessUser, memberMe, agentsAccessUser, owner},
|
|
false: {
|
|
userAdmin, orgUserAdmin, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgAdmin, otherOrgAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Any owner/admin may access notification templates
|
|
Name: "NotificationTemplates",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationTemplate,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser, userAdmin, orgUserAdmin, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgAdmin, otherOrgAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "NotificationMessages",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceNotificationMessage,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Notification preferences are currently not organization-scoped
|
|
// Any owner/admin may access any users' preferences
|
|
// Members may not access other members' preferences
|
|
Name: "NotificationPreferencesOtherUser",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceNotificationPreference.WithOwner(uuid.NewString()), // some other user
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser, templateAdmin, orgUserAdmin, userAdmin,
|
|
orgAdmin, orgAuditor, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
otherOrgAdmin,
|
|
},
|
|
},
|
|
},
|
|
// All users can create, read, and delete their own webpush notification subscriptions.
|
|
{
|
|
Name: "WebpushSubscription",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceWebpushSubscription.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {orgAdmin, otherOrgAdmin, orgAuditor, otherOrgAuditor, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, userAdmin, orgUserAdmin, otherOrgUserAdmin},
|
|
},
|
|
},
|
|
// AnyOrganization tests
|
|
{
|
|
Name: "CreateOrgMember",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceOrganizationMember.AnyOrganization(),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin, orgAdmin, otherOrgAdmin, orgUserAdmin, otherOrgUserAdmin},
|
|
false: {
|
|
memberMe, agentsAccessUser, templateAdmin,
|
|
orgTemplateAdmin, orgAuditor,
|
|
otherOrgAuditor, otherOrgTemplateAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateTemplateAnyOrg",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceTemplate.AnyOrganization(),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin, orgAdmin, otherOrgAdmin},
|
|
false: {
|
|
userAdmin, memberMe, agentsAccessUser,
|
|
orgAuditor, orgUserAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CreateWorkspaceAnyOrg",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceWorkspace.AnyOrganization().WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, otherOrgAdmin, orgWorkspaceAccessUser},
|
|
false: {
|
|
memberMe, agentsAccessUser, userAdmin, templateAdmin,
|
|
orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "CryptoKeys",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionRead},
|
|
Resource: rbac.ResourceCryptoKey,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "IDPSyncSettings",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceIdpsyncSettings.InOrg(orgID),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, orgUserAdmin, userAdmin},
|
|
false: {
|
|
otherOrgAdmin,
|
|
memberMe, agentsAccessUser, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "OrganizationIDPSyncSettings",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceIdpsyncSettings,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, userAdmin},
|
|
false: {
|
|
orgAdmin, orgUserAdmin,
|
|
otherOrgAdmin,
|
|
memberMe, agentsAccessUser, templateAdmin,
|
|
orgAuditor, orgTemplateAdmin,
|
|
otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ResourceMonitor",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceWorkspaceAgentResourceMonitor,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "WorkspaceAgentDevcontainers",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceWorkspaceAgentDevcontainers,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ConnectionLogs",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceConnectionLog,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
// Only the user themselves can access their own secrets — no one else.
|
|
{
|
|
Name: "UserSecrets",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceUserSecret.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {
|
|
owner, orgAdmin,
|
|
otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
// Skills are user-authored instructions, not secrets. Owners can inspect
|
|
// and delete them, but only the user can create or update them.
|
|
{
|
|
Name: "UserSkillsReadDelete",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceUserSkill.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {
|
|
orgAdmin,
|
|
otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "UserSkillsCreateUpdate",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceUserSkill.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {memberMe, agentsAccessUser, orgWorkspaceAccessUser},
|
|
false: {
|
|
owner, orgAdmin,
|
|
otherOrgAdmin, orgAuditor, orgUserAdmin, orgTemplateAdmin,
|
|
templateAdmin, userAdmin, otherOrgAuditor, otherOrgUserAdmin, otherOrgTemplateAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "UsageEvents",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceUsageEvent,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {
|
|
owner,
|
|
memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
orgWorkspaceAccessUser,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Members can create/update records but can't read them afterwards.
|
|
Name: "AIBridgeInterceptionsCreateUpdate",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate},
|
|
Resource: rbac.ResourceAibridgeInterception.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser},
|
|
false: {
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Only owners and site-wide auditors can view interceptions and their sub-resources.
|
|
Name: "AIBridgeInterceptionsRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceAibridgeInterception.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, auditor},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Only owners can manage AI providers. Provider
|
|
// configuration is deployment-wide and includes secret
|
|
// material (api_key, settings) so it is not exposed to
|
|
// org admins or auditors.
|
|
Name: "AIProviders",
|
|
Actions: crud,
|
|
Resource: rbac.ResourceAIProvider,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Only owners can manage AI Gateway keys. They hold
|
|
// a hashed bearer secret used to authenticate Gateway
|
|
// replicas to coderd. Keys are deployment-wide.
|
|
Name: "AIGatewayKey",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionDelete},
|
|
Resource: rbac.ResourceAIGatewayKey,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Updating an AI Gateway key records last-used liveness when a
|
|
// Gateway replica authenticates. It is reserved for the system
|
|
// actor, so no user-facing role, including owner, is authorized.
|
|
Name: "AIGatewayKeyUpdate",
|
|
Actions: []policy.Action{policy.ActionUpdate},
|
|
Resource: rbac.ResourceAIGatewayKey,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {
|
|
owner,
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "BoundaryUsage",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
|
|
Resource: rbac.ResourceBoundaryUsage,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "AiSeat",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead},
|
|
Resource: rbac.ResourceAiSeat,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
false: {owner, setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "AiModelPrice",
|
|
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceAiModelPrice,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner},
|
|
false: {setOtherOrg, setOrgNotMe, memberMe, agentsAccessUser, templateAdmin, userAdmin, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
// Boundary logs: members can create logs they own (user-scoped).
|
|
// memberMe and agentsAccessUser have ID == currentUser, so they
|
|
// match the resource owner. Other subjects have different IDs.
|
|
Name: "BoundaryLogCreate",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceBoundaryLog.WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {orgWorkspaceAccessUser, memberMe, agentsAccessUser},
|
|
false: {
|
|
owner,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor, auditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Cross-user isolation: no subject can create boundary logs
|
|
// owned by a different user. The resource owner is a random
|
|
// UUID that does not match any test subject's ID.
|
|
Name: "BoundaryLogCreateOther",
|
|
Actions: []policy.Action{policy.ActionCreate},
|
|
Resource: rbac.ResourceBoundaryLog.WithOwner(uuid.New().String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {
|
|
orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor, auditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Boundary logs: only DBPurge can delete. No human role
|
|
// has delete; DBPurge is a system subject outside this matrix.
|
|
Name: "BoundaryLogDelete",
|
|
Actions: []policy.Action{policy.ActionDelete},
|
|
Resource: rbac.ResourceBoundaryLog,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {},
|
|
false: {
|
|
orgWorkspaceAccessUser, owner, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor, auditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
// Boundary logs: owner and auditor get read.
|
|
Name: "BoundaryLogRead",
|
|
Actions: []policy.Action{policy.ActionRead},
|
|
Resource: rbac.ResourceBoundaryLog,
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, auditor},
|
|
false: {
|
|
orgWorkspaceAccessUser, memberMe, agentsAccessUser,
|
|
orgAdmin, otherOrgAdmin,
|
|
orgAuditor, otherOrgAuditor,
|
|
templateAdmin, orgTemplateAdmin, otherOrgTemplateAdmin,
|
|
userAdmin, orgUserAdmin, otherOrgUserAdmin,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Name: "ChatUsageCRU",
|
|
Actions: []policy.Action{policy.ActionCreate, policy.ActionRead, policy.ActionUpdate},
|
|
Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, agentsAccessUser},
|
|
false: {setOtherOrg, memberMe, orgMemberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ChatUsageShare",
|
|
Actions: []policy.Action{policy.ActionShare},
|
|
Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin, agentsAccessUser},
|
|
false: {setOtherOrg, memberMe, orgMemberMe, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
{
|
|
Name: "ChatUsageDelete",
|
|
Actions: []policy.Action{policy.ActionDelete},
|
|
Resource: rbac.ResourceChat.WithID(uuid.New()).InOrg(orgID).WithOwner(currentUser.String()),
|
|
AuthorizeMap: map[bool][]hasAuthSubjects{
|
|
true: {owner, orgAdmin},
|
|
false: {setOtherOrg, memberMe, orgMemberMe, agentsAccessUser, userAdmin, templateAdmin, orgTemplateAdmin, orgUserAdmin, orgAuditor, orgWorkspaceAccessUser},
|
|
},
|
|
},
|
|
}
|
|
// Build coverage set from test case definitions statically,
|
|
// so we don't need shared mutable state during execution.
|
|
// This allows subtests to run in parallel.
|
|
coveredPermissions := make(map[string]map[policy.Action]bool)
|
|
for _, c := range testCases {
|
|
for _, action := range c.Actions {
|
|
if coveredPermissions[c.Resource.Type] == nil {
|
|
coveredPermissions[c.Resource.Type] = make(map[policy.Action]bool)
|
|
}
|
|
coveredPermissions[c.Resource.Type][action] = true
|
|
}
|
|
}
|
|
|
|
// Check coverage: every permission in policy.RBACPermissions must
|
|
// be covered by at least one test case.
|
|
for rtype, perms := range policy.RBACPermissions {
|
|
t.Run(fmt.Sprintf("%s-AllActions", rtype), func(t *testing.T) {
|
|
t.Parallel()
|
|
for action := range perms.Actions {
|
|
assert.True(t, coveredPermissions[rtype][action],
|
|
"action %q on type %q is not tested", action, rtype)
|
|
}
|
|
})
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
remainingSubjs := make(map[string]struct{})
|
|
for _, subj := range requiredSubjects {
|
|
remainingSubjs[subj.Name] = struct{}{}
|
|
}
|
|
|
|
for _, action := range c.Actions {
|
|
err := c.Resource.ValidAction(action)
|
|
if !assert.NoError(t, err, "%q is not a valid action for type %q", action, c.Resource.Type) {
|
|
continue
|
|
}
|
|
|
|
for result, sets := range c.AuthorizeMap {
|
|
subjs := make([]authSubject, 0)
|
|
for _, set := range sets {
|
|
subjs = append(subjs, set.Subjects()...)
|
|
}
|
|
used := make(map[string]bool)
|
|
|
|
for _, subj := range subjs {
|
|
if _, ok := used[subj.Name]; ok {
|
|
assert.False(t, true, "duplicate subject %q", subj.Name)
|
|
}
|
|
used[subj.Name] = true
|
|
|
|
delete(remainingSubjs, subj.Name)
|
|
msg := fmt.Sprintf("%s as %q doing %q on %q", c.Name, subj.Name, action, c.Resource.Type)
|
|
// TODO: scopey
|
|
actor := subj.Actor
|
|
// Actor is missing some fields
|
|
if actor.Scope == nil {
|
|
actor.Scope = rbac.ScopeAll
|
|
}
|
|
|
|
err := auth.Authorize(context.Background(), actor, action, c.Resource)
|
|
if result {
|
|
assert.NoError(t, err, fmt.Sprintf("Should pass: %s", msg))
|
|
} else {
|
|
assert.ErrorContains(t, err, "forbidden", fmt.Sprintf("Should fail: %s", msg))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
require.Empty(t, remainingSubjs, "test should cover all subjects")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsOrgRole(t *testing.T) {
|
|
t.Parallel()
|
|
randomUUID, err := uuid.Parse("cad8c09d-c099-4ec7-9263-7d52b1a3997a")
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
Identifier rbac.RoleIdentifier
|
|
OrgRole bool
|
|
OrgID uuid.UUID
|
|
}{
|
|
// Not org roles
|
|
{Identifier: rbac.RoleOwner()},
|
|
{Identifier: rbac.RoleMember()},
|
|
{Identifier: rbac.RoleAuditor()},
|
|
{
|
|
Identifier: rbac.RoleIdentifier{},
|
|
OrgRole: false,
|
|
},
|
|
|
|
// Org roles
|
|
{
|
|
Identifier: rbac.ScopedRoleOrgAdmin(randomUUID),
|
|
OrgRole: true,
|
|
OrgID: randomUUID,
|
|
},
|
|
{
|
|
Identifier: rbac.ScopedRoleOrgMember(randomUUID),
|
|
OrgRole: true,
|
|
OrgID: randomUUID,
|
|
},
|
|
}
|
|
|
|
// nolint:paralleltest
|
|
for _, c := range testCases {
|
|
t.Run(c.Identifier.String(), func(t *testing.T) {
|
|
t.Parallel()
|
|
ok := c.Identifier.IsOrgRole()
|
|
require.Equal(t, c.OrgRole, ok, "match expected org role")
|
|
require.Equal(t, c.OrgID, c.Identifier.OrganizationID, "match expected org id")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListRoles(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
siteRoles := rbac.SiteBuiltInRoles()
|
|
siteRoleNames := make([]string, 0, len(siteRoles))
|
|
for _, role := range siteRoles {
|
|
siteRoleNames = append(siteRoleNames, role.Identifier.Name)
|
|
}
|
|
|
|
// If this test is ever failing, just update the list to the roles
|
|
// expected from the builtin set.
|
|
// Always use constant strings, as if the names change, we need to write
|
|
// a SQL migration to change the name on the backend.
|
|
require.ElementsMatch(t, []string{
|
|
"owner",
|
|
"member",
|
|
"auditor",
|
|
"template-admin",
|
|
"user-admin",
|
|
},
|
|
siteRoleNames)
|
|
orgID := uuid.New()
|
|
orgRoles := rbac.OrganizationRoles(orgID)
|
|
orgRoleNames := make([]string, 0, len(orgRoles))
|
|
for _, role := range orgRoles {
|
|
orgRoleNames = append(orgRoleNames, role.Identifier.String())
|
|
}
|
|
|
|
require.ElementsMatch(t, []string{
|
|
fmt.Sprintf("organization-admin:%s", orgID.String()),
|
|
fmt.Sprintf("organization-auditor:%s", orgID.String()),
|
|
fmt.Sprintf("organization-user-admin:%s", orgID.String()),
|
|
fmt.Sprintf("organization-template-admin:%s", orgID.String()),
|
|
fmt.Sprintf("organization-workspace-creation-ban:%s", orgID.String()),
|
|
fmt.Sprintf("organization-workspace-access:%s", orgID.String()),
|
|
fmt.Sprintf("agents-access:%s", orgID.String()),
|
|
},
|
|
orgRoleNames)
|
|
}
|
|
|
|
func TestChangeSet(t *testing.T) {
|
|
t.Parallel()
|
|
testCases := []struct {
|
|
Name string
|
|
From []string
|
|
To []string
|
|
ExpAdd []string
|
|
ExpRemove []string
|
|
}{
|
|
{
|
|
Name: "Empty",
|
|
},
|
|
{
|
|
Name: "Same",
|
|
From: []string{"a", "b", "c"},
|
|
To: []string{"a", "b", "c"},
|
|
ExpAdd: []string{},
|
|
ExpRemove: []string{},
|
|
},
|
|
{
|
|
Name: "AllRemoved",
|
|
From: []string{"a", "b", "c"},
|
|
ExpRemove: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "AllAdded",
|
|
To: []string{"a", "b", "c"},
|
|
ExpAdd: []string{"a", "b", "c"},
|
|
},
|
|
{
|
|
Name: "AddAndRemove",
|
|
From: []string{"a", "b", "c"},
|
|
To: []string{"a", "b", "d", "e"},
|
|
ExpAdd: []string{"d", "e"},
|
|
ExpRemove: []string{"c"},
|
|
},
|
|
}
|
|
|
|
convert := func(s []string) rbac.RoleIdentifiers {
|
|
tmp := make([]rbac.RoleIdentifier, 0, len(s))
|
|
for _, e := range s {
|
|
tmp = append(tmp, rbac.RoleIdentifier{Name: e})
|
|
}
|
|
return tmp
|
|
}
|
|
|
|
for _, c := range testCases {
|
|
t.Run(c.Name, func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
add, remove := rbac.ChangeRoleSet(convert(c.From), convert(c.To))
|
|
require.ElementsMatch(t, convert(c.ExpAdd), add, "expect added")
|
|
require.ElementsMatch(t, convert(c.ExpRemove), remove, "expect removed")
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestWorkspaceAgentScopeBoundaryLog verifies that a real workspace agent
|
|
// scope (not ScopeAll) can create boundary logs for its own owner but
|
|
// cannot create them for other users, and cannot read or delete them.
|
|
func TestWorkspaceAgentScopeBoundaryLog(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
|
|
|
ownerID := uuid.New()
|
|
otherOwnerID := uuid.New()
|
|
workspaceID := uuid.New()
|
|
templateID := uuid.New()
|
|
versionID := uuid.New()
|
|
|
|
agentScope := rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
|
|
WorkspaceID: workspaceID,
|
|
OwnerID: ownerID,
|
|
TemplateID: templateID,
|
|
VersionID: versionID,
|
|
})
|
|
|
|
memberRole, err := rbac.RoleByName(rbac.RoleMember())
|
|
require.NoError(t, err)
|
|
|
|
agent := rbac.Subject{
|
|
ID: ownerID.String(),
|
|
Roles: rbac.Roles{memberRole},
|
|
Scope: agentScope,
|
|
}.WithCachedASTValue()
|
|
|
|
// Agent can create boundary logs for its own owner.
|
|
err = auth.Authorize(context.Background(), agent, policy.ActionCreate,
|
|
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
|
require.NoError(t, err, "agent should create boundary logs for own owner")
|
|
|
|
// Agent cannot create boundary logs for a different owner.
|
|
err = auth.Authorize(context.Background(), agent, policy.ActionCreate,
|
|
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
|
require.Error(t, err, "agent must not create boundary logs for other owner")
|
|
|
|
// Agent cannot read boundary logs (even its own owner's).
|
|
err = auth.Authorize(context.Background(), agent, policy.ActionRead,
|
|
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
|
require.Error(t, err, "agent must not read boundary logs")
|
|
|
|
// Agent cannot delete boundary logs (even its own owner's).
|
|
err = auth.Authorize(context.Background(), agent, policy.ActionDelete,
|
|
rbac.ResourceBoundaryLog.WithOwner(ownerID.String()))
|
|
require.Error(t, err, "agent must not delete boundary logs")
|
|
|
|
// When the workspace owner is a site admin, the agent scope
|
|
// wildcard for boundary_log combined with the owner role's site-level
|
|
// read grant means the agent CAN read all boundary logs. This is an
|
|
// accepted consequence of the wildcard scope needed for creation.
|
|
ownerRole, err := rbac.RoleByName(rbac.RoleOwner())
|
|
require.NoError(t, err)
|
|
|
|
adminAgent := rbac.Subject{
|
|
ID: ownerID.String(),
|
|
Roles: rbac.Roles{memberRole, ownerRole},
|
|
Scope: agentScope,
|
|
}.WithCachedASTValue()
|
|
|
|
// Admin-owned agent CAN read boundary logs due to site-level owner
|
|
// role + wildcard scope.
|
|
err = auth.Authorize(context.Background(), adminAgent, policy.ActionRead,
|
|
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
|
require.NoError(t, err, "admin agent inherits site-level read via owner role")
|
|
|
|
// Admin-owned agent still cannot create boundary logs for another owner
|
|
// because member-level create is user-scoped (subject.id must match owner).
|
|
err = auth.Authorize(context.Background(), adminAgent, policy.ActionCreate,
|
|
rbac.ResourceBoundaryLog.WithOwner(otherOwnerID.String()))
|
|
require.Error(t, err, "admin agent must not create boundary logs for other owner")
|
|
}
|
|
|
|
// TestDBPurgeBoundaryLogDelete verifies that the DBPurge system subject
|
|
// can delete boundary logs but cannot create or read them.
|
|
func TestDBPurgeBoundaryLogDelete(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
auth := rbac.NewStrictAuthorizer(prometheus.NewRegistry())
|
|
|
|
// Build the DBPurge subject the same way dbauthz does.
|
|
dbPurge := rbac.Subject{
|
|
Type: rbac.SubjectTypeDBPurge,
|
|
FriendlyName: "DB Purge",
|
|
ID: uuid.Nil.String(),
|
|
Roles: rbac.Roles([]rbac.Role{
|
|
{
|
|
Identifier: rbac.RoleIdentifier{Name: "dbpurge"},
|
|
DisplayName: "DB Purge Daemon",
|
|
Site: rbac.Permissions(map[string][]policy.Action{
|
|
rbac.ResourceBoundaryLog.Type: {policy.ActionDelete},
|
|
}),
|
|
User: []rbac.Permission{},
|
|
ByOrgID: map[string]rbac.OrgPermissions{},
|
|
},
|
|
}),
|
|
Scope: rbac.ScopeAll,
|
|
}.WithCachedASTValue()
|
|
|
|
// DBPurge can delete boundary logs.
|
|
err := auth.Authorize(context.Background(), dbPurge, policy.ActionDelete,
|
|
rbac.ResourceBoundaryLog)
|
|
require.NoError(t, err, "DBPurge should delete boundary logs")
|
|
|
|
// DBPurge cannot create boundary logs.
|
|
err = auth.Authorize(context.Background(), dbPurge, policy.ActionCreate,
|
|
rbac.ResourceBoundaryLog.WithOwner(uuid.New().String()))
|
|
require.Error(t, err, "DBPurge must not create boundary logs")
|
|
|
|
// DBPurge cannot read boundary logs.
|
|
err = auth.Authorize(context.Background(), dbPurge, policy.ActionRead,
|
|
rbac.ResourceBoundaryLog)
|
|
require.Error(t, err, "DBPurge must not read boundary logs")
|
|
}
|