mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
refactor(coderd/rbac): drop dead Member-scoped perms from org roles
Member-level perms in OrgPermissions only fire when
input.object.owner == input.subject.id (see the org_member rule in
coderd/rbac/policy.rego). Resources whose RBACObject() does not set
WithOwner(...) at production call sites can never satisfy that
condition; granting them at Member scope is dead code. PR 1's
enumeration inherited these from the legacy allPermsExcept(...)
wildcard. This commit drops them so the floor matches its documented
scope and adds an "Intentionally omitted" block in roles.go listing
each removed type and the reason it stays out, for posterity.
Removed from both OrgMemberPermissions and OrgServiceAccountPermissions
Member maps:
- ResourceTemplate {read, use}
Template.RBACObject sets InOrg and ACLs but no Owner. Org-member
template.use is granted via the "Everyone" ACL path
(acl_group_list[org_owner] populated on each template's
GroupACL); that is the rule that fires in createWorkspace, not
the Member-level grant.
- ResourceGroup {read}
Group.RBACObject sets a per-group GroupACL granting read to the
group's own ID, but no Owner. "Groups I'm a member of can read
themselves" is the ACL path. Reading other groups requires
a higher role.
- ResourceWorkspaceProxy {read}
WorkspaceProxy.RBACObject sets only WithID. All production call
sites use the bare resource; Member-level grant never fires.
- ResourceProvisionerJobs {*}
No DB model implements RBACObject. Handler call sites use
.InOrg(org.ID) only; coderd/provisionerjobs.go:100 documents
the intent as "only owners and template admins can access
provisioner jobs."
- ResourceWorkspaceAgentResourceMonitor {*}
Dbauthz call sites use the bare resource for system / telemetry
reads. Owner-scoped checks (e.g.
FetchVolumesResourceMonitorsByAgentID) route through the
workspace object instead, so the Member-level monitor grant is
never the path that authorizes.
- ResourceWorkspaceAgentDevcontainers {*}
Dbauthz call sites use the bare resource. Agent-side perms come
from system roles.
- ResourceTailnetCoordinator {*}
Dbauthz call sites use the bare resource. Tailnet ops are
granted to system / agent roles.
- ResourceReplicas {read}
Bare resource at the single call site in
enterprise/coderd/replicas.go; Member-level never fires.
Behavior-preserving: all eight grants were also dead under the
legacy allPermsExcept(...) wildcard. The rbac, dbauthz, coderd, and
enterprise/coderd test suites pass at the same scope verified for
the initial PR 1 commit.
This commit is contained in:
+62
-47
@@ -1058,6 +1058,12 @@ func OrgMemberPermissions(org OrgSettings) OrgRolePermissions {
|
||||
// Enumerate the per-member resources explicitly so new resources do
|
||||
// not auto-grant to org members. Adding a resource to the codebase
|
||||
// requires an explicit decision to expose it here.
|
||||
//
|
||||
// Member-level grants only fire when input.object.owner ==
|
||||
// input.subject.id (see the org_member rule in
|
||||
// coderd/rbac/policy.rego). Only resources whose RBACObject() calls
|
||||
// WithOwner(...) at production call sites belong here; see the
|
||||
// "Intentionally omitted" block at the bottom.
|
||||
memberPerms := Permissions(map[string][]policy.Action{
|
||||
// Workspace lifecycle on resources owned by this member.
|
||||
ResourceWorkspace.Type: ResourceWorkspace.AvailableActions(),
|
||||
@@ -1075,48 +1081,58 @@ func OrgMemberPermissions(org OrgSettings) OrgRolePermissions {
|
||||
policy.ActionUpdateAgent,
|
||||
},
|
||||
|
||||
// Workspace runtime support: proxies, agent monitors,
|
||||
// devcontainer setup, and tailnet coordination.
|
||||
ResourceWorkspaceProxy.Type: {policy.ActionRead},
|
||||
ResourceWorkspaceAgentResourceMonitor.Type: ResourceWorkspaceAgentResourceMonitor.AvailableActions(),
|
||||
ResourceWorkspaceAgentDevcontainers.Type: ResourceWorkspaceAgentDevcontainers.AvailableActions(),
|
||||
ResourceTailnetCoordinator.Type: ResourceTailnetCoordinator.AvailableActions(),
|
||||
|
||||
// Apply templates; full template lifecycle is restricted to
|
||||
// template-admin.
|
||||
ResourceTemplate.Type: {policy.ActionRead, policy.ActionUse},
|
||||
|
||||
// Upload and read template files used during workspace build.
|
||||
// Upload and read template files the member created during
|
||||
// workspace build (File.RBACObject sets WithOwner(CreatedBy)).
|
||||
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
|
||||
// Provisioner jobs back workspace builds.
|
||||
ResourceProvisionerJobs.Type: ResourceProvisionerJobs.AvailableActions(),
|
||||
|
||||
// Tasks ride along with workspaces.
|
||||
// Tasks ride along with workspaces and are owner-scoped.
|
||||
ResourceTask.Type: ResourceTask.AvailableActions(),
|
||||
|
||||
// Read groups and group memberships for ACL evaluation.
|
||||
ResourceGroup.Type: {policy.ActionRead},
|
||||
// Read-self group-membership record. GroupMember.RBACObject
|
||||
// sets WithOwner to the user's own ID.
|
||||
ResourceGroupMember.Type: {policy.ActionRead},
|
||||
|
||||
// Read-self org-member record.
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
|
||||
// Members can create and update AI Bridge interceptions but
|
||||
// cannot read them back. Chat access requires the agents-access
|
||||
// role and is intentionally not granted here.
|
||||
// Members can create and update AI Bridge interceptions they
|
||||
// initiate (dbauthz layer sets WithOwner(InitiatorID)) but
|
||||
// cannot read them back. Chat access requires the
|
||||
// agents-access role and is intentionally not granted here.
|
||||
ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionUpdate},
|
||||
|
||||
// Own session tokens and workspace agent auth keys.
|
||||
ResourceApiKey.Type: ResourceApiKey.AvailableActions(),
|
||||
|
||||
// User-scoped notification surfaces.
|
||||
// User-scoped notification surfaces. All three resources are
|
||||
// addressed by WithOwner(user_id) at the call sites.
|
||||
ResourceNotificationMessage.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
ResourceNotificationPreference.Type: ResourceNotificationPreference.AvailableActions(),
|
||||
ResourceInboxNotification.Type: ResourceInboxNotification.AvailableActions(),
|
||||
|
||||
// Replica metadata (read-only is the only defined action).
|
||||
ResourceReplicas.Type: {policy.ActionRead},
|
||||
// Intentionally omitted at Member scope (resources without an
|
||||
// Owner field on their RBACObject; Member-level grants never
|
||||
// fire for them). Listed here so a future maintainer who sees
|
||||
// these dropped relative to the legacy allPermsExcept(...)
|
||||
// wildcard does not "restore" them:
|
||||
//
|
||||
// - ResourceTemplate: templates have no owner. Org-member
|
||||
// template.use is authorized via the ACL path
|
||||
// (acl_group_list[org_owner] "Everyone" group, populated
|
||||
// on each template's GroupACL).
|
||||
// - ResourceGroup: groups have no owner. "Groups I'm a
|
||||
// member of can read themselves" is granted via the
|
||||
// per-group GroupACL.
|
||||
// - ResourceWorkspaceProxy, ResourceProvisionerJobs,
|
||||
// ResourceWorkspaceAgentResourceMonitor,
|
||||
// ResourceWorkspaceAgentDevcontainers,
|
||||
// ResourceTailnetCoordinator, ResourceReplicas: these
|
||||
// resources have no DB model that sets Owner; all
|
||||
// production call sites use the bare resource or
|
||||
// .InOrg(...) only. Access for these flows through Org
|
||||
// perms on the appropriate role (e.g. ProvisionerDaemon
|
||||
// above), or through system / agent / template-admin
|
||||
// roles defined elsewhere.
|
||||
})
|
||||
|
||||
if org.ShareableWorkspaceOwners != ShareableWorkspaceOwnersEveryone {
|
||||
@@ -1167,6 +1183,12 @@ func OrgServiceAccountPermissions(org OrgSettings) OrgRolePermissions {
|
||||
// service account-scoped permissions (resources owned by the
|
||||
// service account). Enumerated explicitly so new resources do not
|
||||
// auto-grant to service accounts.
|
||||
//
|
||||
// Member-level grants only fire when input.object.owner ==
|
||||
// input.subject.id (see the org_member rule in
|
||||
// coderd/rbac/policy.rego). Only resources whose RBACObject() calls
|
||||
// WithOwner(...) at production call sites belong here; see the
|
||||
// "Intentionally omitted" block at the bottom.
|
||||
memberPerms := Permissions(map[string][]policy.Action{
|
||||
// Workspace lifecycle on resources owned by this service account.
|
||||
ResourceWorkspace.Type: ResourceWorkspace.AvailableActions(),
|
||||
@@ -1184,47 +1206,40 @@ func OrgServiceAccountPermissions(org OrgSettings) OrgRolePermissions {
|
||||
policy.ActionUpdateAgent,
|
||||
},
|
||||
|
||||
// Workspace runtime support.
|
||||
ResourceWorkspaceProxy.Type: {policy.ActionRead},
|
||||
ResourceWorkspaceAgentResourceMonitor.Type: ResourceWorkspaceAgentResourceMonitor.AvailableActions(),
|
||||
ResourceWorkspaceAgentDevcontainers.Type: ResourceWorkspaceAgentDevcontainers.AvailableActions(),
|
||||
ResourceTailnetCoordinator.Type: ResourceTailnetCoordinator.AvailableActions(),
|
||||
|
||||
// Apply templates; full template lifecycle is restricted to
|
||||
// template-admin.
|
||||
ResourceTemplate.Type: {policy.ActionRead, policy.ActionUse},
|
||||
|
||||
// Upload and read template files used during workspace build.
|
||||
// Upload and read template files the service account created
|
||||
// during workspace build (File.RBACObject sets
|
||||
// WithOwner(CreatedBy)).
|
||||
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
|
||||
|
||||
// Provisioner jobs back workspace builds.
|
||||
ResourceProvisionerJobs.Type: ResourceProvisionerJobs.AvailableActions(),
|
||||
|
||||
// Tasks ride along with workspaces.
|
||||
// Tasks ride along with workspaces and are owner-scoped.
|
||||
ResourceTask.Type: ResourceTask.AvailableActions(),
|
||||
|
||||
// Read groups and group memberships for ACL evaluation.
|
||||
ResourceGroup.Type: {policy.ActionRead},
|
||||
// Read-self group-membership record. GroupMember.RBACObject
|
||||
// sets WithOwner to the user's own ID.
|
||||
ResourceGroupMember.Type: {policy.ActionRead},
|
||||
|
||||
// Read-self org-member record.
|
||||
ResourceOrganizationMember.Type: {policy.ActionRead},
|
||||
|
||||
// Service accounts can create and update AI Bridge interceptions
|
||||
// but cannot read them back. Chat access requires the
|
||||
// agents-access role and is intentionally not granted here.
|
||||
// Service accounts can create and update AI Bridge
|
||||
// interceptions they initiate (dbauthz layer sets
|
||||
// WithOwner(InitiatorID)) but cannot read them back. Chat
|
||||
// access requires the agents-access role and is intentionally
|
||||
// not granted here.
|
||||
ResourceAibridgeInterception.Type: {policy.ActionCreate, policy.ActionUpdate},
|
||||
|
||||
// Own session tokens and workspace agent auth keys.
|
||||
ResourceApiKey.Type: ResourceApiKey.AvailableActions(),
|
||||
|
||||
// User-scoped notification surfaces.
|
||||
// User-scoped notification surfaces. All three resources are
|
||||
// addressed by WithOwner(user_id) at the call sites.
|
||||
ResourceNotificationMessage.Type: {policy.ActionRead, policy.ActionUpdate},
|
||||
ResourceNotificationPreference.Type: ResourceNotificationPreference.AvailableActions(),
|
||||
ResourceInboxNotification.Type: ResourceInboxNotification.AvailableActions(),
|
||||
|
||||
// Replica metadata (read-only is the only defined action).
|
||||
ResourceReplicas.Type: {policy.ActionRead},
|
||||
// Intentionally omitted at Member scope. See
|
||||
// OrgMemberPermissions above for the rationale; the service
|
||||
// account role mirrors the same partition.
|
||||
})
|
||||
|
||||
return OrgRolePermissions{Org: orgPerms, Member: memberPerms}
|
||||
|
||||
Reference in New Issue
Block a user