diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 6ee189e182..617678ab99 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -11932,6 +11932,11 @@ const docTemplate = `{ "tailnet_coordinator:delete", "tailnet_coordinator:read", "tailnet_coordinator:update", + "task:*", + "task:create", + "task:delete", + "task:read", + "task:update", "template:*", "template:create", "template:delete", @@ -12123,6 +12128,11 @@ const docTemplate = `{ "APIKeyScopeTailnetCoordinatorDelete", "APIKeyScopeTailnetCoordinatorRead", "APIKeyScopeTailnetCoordinatorUpdate", + "APIKeyScopeTaskAll", + "APIKeyScopeTaskCreate", + "APIKeyScopeTaskDelete", + "APIKeyScopeTaskRead", + "APIKeyScopeTaskUpdate", "APIKeyScopeTemplateAll", "APIKeyScopeTemplateCreate", "APIKeyScopeTemplateDelete", @@ -16977,6 +16987,7 @@ const docTemplate = `{ "replicas", "system", "tailnet_coordinator", + "task", "template", "usage_event", "user", @@ -17020,6 +17031,7 @@ const docTemplate = `{ "ResourceReplicas", "ResourceSystem", "ResourceTailnetCoordinator", + "ResourceTask", "ResourceTemplate", "ResourceUsageEvent", "ResourceUser", diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 48aaadadb9..83a51e31b8 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -10636,6 +10636,11 @@ "tailnet_coordinator:delete", "tailnet_coordinator:read", "tailnet_coordinator:update", + "task:*", + "task:create", + "task:delete", + "task:read", + "task:update", "template:*", "template:create", "template:delete", @@ -10827,6 +10832,11 @@ "APIKeyScopeTailnetCoordinatorDelete", "APIKeyScopeTailnetCoordinatorRead", "APIKeyScopeTailnetCoordinatorUpdate", + "APIKeyScopeTaskAll", + "APIKeyScopeTaskCreate", + "APIKeyScopeTaskDelete", + "APIKeyScopeTaskRead", + "APIKeyScopeTaskUpdate", "APIKeyScopeTemplateAll", "APIKeyScopeTemplateCreate", "APIKeyScopeTemplateDelete", @@ -15499,6 +15509,7 @@ "replicas", "system", "tailnet_coordinator", + "task", "template", "usage_event", "user", @@ -15542,6 +15553,7 @@ "ResourceReplicas", "ResourceSystem", "ResourceTailnetCoordinator", + "ResourceTask", "ResourceTemplate", "ResourceUsageEvent", "ResourceUser", diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index 4f17a89703..716d6e3825 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -219,7 +219,9 @@ var ( rbac.ResourceUser.Type: {policy.ActionRead, policy.ActionReadPersonal, policy.ActionUpdatePersonal}, rbac.ResourceWorkspaceDormant.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStop}, rbac.ResourceWorkspace.Type: {policy.ActionDelete, policy.ActionRead, policy.ActionUpdate, policy.ActionWorkspaceStart, policy.ActionWorkspaceStop, policy.ActionCreateAgent}, - rbac.ResourceApiKey.Type: {policy.WildcardSymbol}, + // Provisionerd needs to read and update tasks associated with workspaces. + rbac.ResourceTask.Type: {policy.ActionRead, policy.ActionUpdate}, + rbac.ResourceApiKey.Type: {policy.WildcardSymbol}, // When org scoped provisioner credentials are implemented, // this can be reduced to read a specific org. rbac.ResourceOrganization.Type: {policy.ActionRead}, diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index b2cac7185b..cef9e37445 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -197,7 +197,12 @@ CREATE TYPE api_key_scope AS ENUM ( 'workspace_agent_devcontainers:*', 'workspace_agent_resource_monitor:*', 'workspace_dormant:*', - 'workspace_proxy:*' + 'workspace_proxy:*', + 'task:create', + 'task:read', + 'task:update', + 'task:delete', + 'task:*' ); CREATE TYPE app_sharing_level AS ENUM ( diff --git a/coderd/database/migrations/000378_add_tasks_rbac.down.sql b/coderd/database/migrations/000378_add_tasks_rbac.down.sql new file mode 100644 index 0000000000..761a8d943f --- /dev/null +++ b/coderd/database/migrations/000378_add_tasks_rbac.down.sql @@ -0,0 +1,3 @@ +-- Revert Tasks RBAC. +-- No-op: enum values remain to avoid churn. Removing enum values requires +-- doing a create/cast/drop cycle which is intentionally omitted here. diff --git a/coderd/database/migrations/000378_add_tasks_rbac.up.sql b/coderd/database/migrations/000378_add_tasks_rbac.up.sql new file mode 100644 index 0000000000..18d81ac443 --- /dev/null +++ b/coderd/database/migrations/000378_add_tasks_rbac.up.sql @@ -0,0 +1,6 @@ +-- Tasks RBAC. +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'task:create'; +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'task:read'; +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'task:update'; +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'task:delete'; +ALTER TYPE api_key_scope ADD VALUE IF NOT EXISTS 'task:*'; diff --git a/coderd/database/modelmethods.go b/coderd/database/modelmethods.go index b6d10569a7..6254125cc1 100644 --- a/coderd/database/modelmethods.go +++ b/coderd/database/modelmethods.go @@ -132,6 +132,13 @@ func (w ConnectionLog) RBACObject() rbac.Object { return obj } +func (t Task) RBACObject() rbac.Object { + return rbac.ResourceTask. + WithID(t.ID). + WithOwner(t.OwnerID.String()). + InOrg(t.OrganizationID) +} + func (s APIKeyScope) ToRBAC() rbac.ScopeName { switch s { case ApiKeyScopeCoderAll: diff --git a/coderd/database/models.go b/coderd/database/models.go index 06c0e98fe4..4f4632569e 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -206,6 +206,11 @@ const ( ApiKeyScopeWorkspaceAgentResourceMonitor APIKeyScope = "workspace_agent_resource_monitor:*" ApiKeyScopeWorkspaceDormant APIKeyScope = "workspace_dormant:*" ApiKeyScopeWorkspaceProxy APIKeyScope = "workspace_proxy:*" + ApiKeyScopeTaskCreate APIKeyScope = "task:create" + ApiKeyScopeTaskRead APIKeyScope = "task:read" + ApiKeyScopeTaskUpdate APIKeyScope = "task:update" + ApiKeyScopeTaskDelete APIKeyScope = "task:delete" + ApiKeyScopeTask APIKeyScope = "task:*" ) func (e *APIKeyScope) Scan(src interface{}) error { @@ -431,7 +436,12 @@ func (e APIKeyScope) Valid() bool { ApiKeyScopeWorkspaceAgentDevcontainers, ApiKeyScopeWorkspaceAgentResourceMonitor, ApiKeyScopeWorkspaceDormant, - ApiKeyScopeWorkspaceProxy: + ApiKeyScopeWorkspaceProxy, + ApiKeyScopeTaskCreate, + ApiKeyScopeTaskRead, + ApiKeyScopeTaskUpdate, + ApiKeyScopeTaskDelete, + ApiKeyScopeTask: return true } return false @@ -626,6 +636,11 @@ func AllAPIKeyScopeValues() []APIKeyScope { ApiKeyScopeWorkspaceAgentResourceMonitor, ApiKeyScopeWorkspaceDormant, ApiKeyScopeWorkspaceProxy, + ApiKeyScopeTaskCreate, + ApiKeyScopeTaskRead, + ApiKeyScopeTaskUpdate, + ApiKeyScopeTaskDelete, + ApiKeyScopeTask, } } diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index d0c78bd480..9efaa0bfe1 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -286,6 +286,16 @@ var ( Type: "tailnet_coordinator", } + // ResourceTask + // Valid Actions + // - "ActionCreate" :: create a new task + // - "ActionDelete" :: delete task + // - "ActionRead" :: read task data or output to view on the UI or CLI + // - "ActionUpdate" :: edit task settings or send input to an existing task + ResourceTask = Object{ + Type: "task", + } + // ResourceTemplate // Valid Actions // - "ActionCreate" :: create a template @@ -430,6 +440,7 @@ func AllResources() []Objecter { ResourceReplicas, ResourceSystem, ResourceTailnetCoordinator, + ResourceTask, ResourceTemplate, ResourceUsageEvent, ResourceUser, diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index 93b0ba4e76..cdc91a134a 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -63,6 +63,13 @@ var workspaceActions = map[Action]ActionDefinition{ ActionDeleteAgent: "delete an existing workspace agent", } +var taskActions = map[Action]ActionDefinition{ + ActionCreate: "create a new task", + ActionRead: "read task data or output to view on the UI or CLI", + ActionUpdate: "edit task settings or send input to an existing task", + ActionDelete: "delete task", +} + // RBACPermissions is indexed by the type var RBACPermissions = map[string]PermissionDefinition{ // Wildcard is every object, and the action "*" provides all actions. @@ -86,6 +93,9 @@ var RBACPermissions = map[string]PermissionDefinition{ "workspace": { Actions: workspaceActions, }, + "task": { + Actions: taskActions, + }, // Dormant workspaces have the same perms as workspaces. "workspace_dormant": { Actions: workspaceActions, diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index 18d521d474..d5bf2e2af8 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -505,6 +505,15 @@ func TestRolePermissions(t *testing.T) { false: {setOtherOrg, userAdmin, memberMe, orgUserAdmin, orgAuditor, orgMemberMe}, }, }, + { + Name: "Task", + Actions: crud, + Resource: rbac.ResourceTask.WithID(uuid.New()).InOrg(orgID).WithOwner(memberMe.Actor.ID), + AuthorizeMap: map[bool][]hasAuthSubjects{ + true: {owner, orgAdmin, orgMemberMe}, + false: {setOtherOrg, userAdmin, templateAdmin, memberMe, orgTemplateAdmin, orgUserAdmin, orgAuditor}, + }, + }, // Some admin style resources { Name: "Licenses", diff --git a/coderd/rbac/scopes_catalog.go b/coderd/rbac/scopes_catalog.go index f8deb57b46..ef4f3186de 100644 --- a/coderd/rbac/scopes_catalog.go +++ b/coderd/rbac/scopes_catalog.go @@ -50,6 +50,13 @@ var externalLowLevel = map[ScopeName]struct{}{ "user_secret:update": {}, "user_secret:delete": {}, "user_secret:*": {}, + + // Tasks + "task:create": {}, + "task:read": {}, + "task:update": {}, + "task:delete": {}, + "task:*": {}, } // Public composite coder:* scopes exposed to users. diff --git a/coderd/rbac/scopes_constants_gen.go b/coderd/rbac/scopes_constants_gen.go index ccd9622cd4..a81ccf7be4 100644 --- a/coderd/rbac/scopes_constants_gen.go +++ b/coderd/rbac/scopes_constants_gen.go @@ -95,6 +95,10 @@ const ( ScopeTailnetCoordinatorDelete ScopeName = "tailnet_coordinator:delete" ScopeTailnetCoordinatorRead ScopeName = "tailnet_coordinator:read" ScopeTailnetCoordinatorUpdate ScopeName = "tailnet_coordinator:update" + ScopeTaskCreate ScopeName = "task:create" + ScopeTaskDelete ScopeName = "task:delete" + ScopeTaskRead ScopeName = "task:read" + ScopeTaskUpdate ScopeName = "task:update" ScopeTemplateCreate ScopeName = "template:create" ScopeTemplateDelete ScopeName = "template:delete" ScopeTemplateRead ScopeName = "template:read" @@ -244,6 +248,10 @@ func (e ScopeName) Valid() bool { ScopeTailnetCoordinatorDelete, ScopeTailnetCoordinatorRead, ScopeTailnetCoordinatorUpdate, + ScopeTaskCreate, + ScopeTaskDelete, + ScopeTaskRead, + ScopeTaskUpdate, ScopeTemplateCreate, ScopeTemplateDelete, ScopeTemplateRead, @@ -394,6 +402,10 @@ func AllScopeNameValues() []ScopeName { ScopeTailnetCoordinatorDelete, ScopeTailnetCoordinatorRead, ScopeTailnetCoordinatorUpdate, + ScopeTaskCreate, + ScopeTaskDelete, + ScopeTaskRead, + ScopeTaskUpdate, ScopeTemplateCreate, ScopeTemplateDelete, ScopeTemplateRead, diff --git a/codersdk/apikey_scopes_gen.go b/codersdk/apikey_scopes_gen.go index 606a704ae4..d0e88bb2d6 100644 --- a/codersdk/apikey_scopes_gen.go +++ b/codersdk/apikey_scopes_gen.go @@ -133,6 +133,11 @@ const ( APIKeyScopeTailnetCoordinatorDelete APIKeyScope = "tailnet_coordinator:delete" APIKeyScopeTailnetCoordinatorRead APIKeyScope = "tailnet_coordinator:read" APIKeyScopeTailnetCoordinatorUpdate APIKeyScope = "tailnet_coordinator:update" + APIKeyScopeTaskAll APIKeyScope = "task:*" + APIKeyScopeTaskCreate APIKeyScope = "task:create" + APIKeyScopeTaskDelete APIKeyScope = "task:delete" + APIKeyScopeTaskRead APIKeyScope = "task:read" + APIKeyScopeTaskUpdate APIKeyScope = "task:update" APIKeyScopeTemplateAll APIKeyScope = "template:*" APIKeyScopeTemplateCreate APIKeyScope = "template:create" APIKeyScopeTemplateDelete APIKeyScope = "template:delete" @@ -214,6 +219,11 @@ var PublicAPIKeyScopes = []APIKeyScope{ APIKeyScopeFileAll, APIKeyScopeFileCreate, APIKeyScopeFileRead, + APIKeyScopeTaskAll, + APIKeyScopeTaskCreate, + APIKeyScopeTaskDelete, + APIKeyScopeTaskRead, + APIKeyScopeTaskUpdate, APIKeyScopeTemplateAll, APIKeyScopeTemplateCreate, APIKeyScopeTemplateDelete, diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index 6a8185e6eb..a48b688a7a 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -35,6 +35,7 @@ const ( ResourceReplicas RBACResource = "replicas" ResourceSystem RBACResource = "system" ResourceTailnetCoordinator RBACResource = "tailnet_coordinator" + ResourceTask RBACResource = "task" ResourceTemplate RBACResource = "template" ResourceUsageEvent RBACResource = "usage_event" ResourceUser RBACResource = "user" @@ -102,6 +103,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceReplicas: {ActionRead}, ResourceSystem: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceTailnetCoordinator: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, + ResourceTask: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceTemplate: {ActionCreate, ActionDelete, ActionRead, ActionUpdate, ActionUse, ActionViewInsights}, ResourceUsageEvent: {ActionCreate, ActionRead, ActionUpdate}, ResourceUser: {ActionCreate, ActionDelete, ActionRead, ActionReadPersonal, ActionUpdate, ActionUpdatePersonal}, diff --git a/docs/reference/api/members.md b/docs/reference/api/members.md index 6e02b90bc6..8fac25e977 100644 --- a/docs/reference/api/members.md +++ b/docs/reference/api/members.md @@ -213,6 +213,7 @@ Status Code **200** | `resource_type` | `replicas` | | `resource_type` | `system` | | `resource_type` | `tailnet_coordinator` | +| `resource_type` | `task` | | `resource_type` | `template` | | `resource_type` | `usage_event` | | `resource_type` | `user` | @@ -386,6 +387,7 @@ Status Code **200** | `resource_type` | `replicas` | | `resource_type` | `system` | | `resource_type` | `tailnet_coordinator` | +| `resource_type` | `task` | | `resource_type` | `template` | | `resource_type` | `usage_event` | | `resource_type` | `user` | @@ -559,6 +561,7 @@ Status Code **200** | `resource_type` | `replicas` | | `resource_type` | `system` | | `resource_type` | `tailnet_coordinator` | +| `resource_type` | `task` | | `resource_type` | `template` | | `resource_type` | `usage_event` | | `resource_type` | `user` | @@ -701,6 +704,7 @@ Status Code **200** | `resource_type` | `replicas` | | `resource_type` | `system` | | `resource_type` | `tailnet_coordinator` | +| `resource_type` | `task` | | `resource_type` | `template` | | `resource_type` | `usage_event` | | `resource_type` | `user` | @@ -1065,6 +1069,7 @@ Status Code **200** | `resource_type` | `replicas` | | `resource_type` | `system` | | `resource_type` | `tailnet_coordinator` | +| `resource_type` | `task` | | `resource_type` | `template` | | `resource_type` | `usage_event` | | `resource_type` | `user` | diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index cb616c02e0..4c8fcba929 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -909,6 +909,11 @@ | `tailnet_coordinator:delete` | | `tailnet_coordinator:read` | | `tailnet_coordinator:update` | +| `task:*` | +| `task:create` | +| `task:delete` | +| `task:read` | +| `task:update` | | `template:*` | | `template:create` | | `template:delete` | @@ -7110,6 +7115,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `replicas` | | `system` | | `tailnet_coordinator` | +| `task` | | `template` | | `usage_event` | | `user` | diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index e2a3948949..0f62160563 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -156,6 +156,12 @@ export const RBACResourceActions: Partial< read: "view info about a Tailnet coordinator", update: "update a Tailnet coordinator", }, + task: { + create: "create a new task", + delete: "delete task", + read: "read task data or output to view on the UI or CLI", + update: "edit task settings or send input to an existing task", + }, template: { create: "create a template", delete: "delete a template", diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index ebb9ed1a83..c84e4ed054 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -247,6 +247,11 @@ export type APIKeyScope = | "tailnet_coordinator:delete" | "tailnet_coordinator:read" | "tailnet_coordinator:update" + | "task:*" + | "task:create" + | "task:delete" + | "task:read" + | "task:update" | "template:*" | "template:create" | "template:delete" @@ -438,6 +443,11 @@ export const APIKeyScopes: APIKeyScope[] = [ "tailnet_coordinator:delete", "tailnet_coordinator:read", "tailnet_coordinator:update", + "task:*", + "task:create", + "task:delete", + "task:read", + "task:update", "template:*", "template:create", "template:delete", @@ -2915,6 +2925,7 @@ export type RBACResource = | "replicas" | "system" | "tailnet_coordinator" + | "task" | "template" | "usage_event" | "user" @@ -2958,6 +2969,7 @@ export const RBACResources: RBACResource[] = [ "replicas", "system", "tailnet_coordinator", + "task", "template", "usage_event", "user",