From 162b3a52f11d5747f7fe65c9d3be63d4f49ce191 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 8 Jan 2025 12:38:34 -0600 Subject: [PATCH 01/13] chore: add 'use' verb to template object --- coderd/rbac/object_gen.go | 1 + coderd/rbac/policy/policy.go | 1 + codersdk/rbacresources_gen.go | 2 +- site/src/api/rbacresourcesGenerated.ts | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index d1ebd1c8f56a1..8076eb819be62 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -256,6 +256,7 @@ var ( // - "ActionDelete" :: delete a template // - "ActionRead" :: read template // - "ActionUpdate" :: update a template + // - "ActionUse" :: use the template to create a workspace // - "ActionViewInsights" :: view insights ResourceTemplate = Object{ Type: "template", diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index 2691eed9fe0a9..948a307a9d296 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -135,6 +135,7 @@ var RBACPermissions = map[string]PermissionDefinition{ Actions: map[Action]ActionDefinition{ ActionCreate: actDef("create a template"), // TODO: Create a use permission maybe? + ActionUse: actDef("use the template to create a workspace"), ActionRead: actDef("read template"), ActionUpdate: actDef("update a template"), ActionDelete: actDef("delete a template"), diff --git a/codersdk/rbacresources_gen.go b/codersdk/rbacresources_gen.go index ced2568719578..b90da3cbdb2f1 100644 --- a/codersdk/rbacresources_gen.go +++ b/codersdk/rbacresources_gen.go @@ -86,7 +86,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{ ResourceReplicas: {ActionRead}, ResourceSystem: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, ResourceTailnetCoordinator: {ActionCreate, ActionDelete, ActionRead, ActionUpdate}, - ResourceTemplate: {ActionCreate, ActionDelete, ActionRead, ActionUpdate, ActionViewInsights}, + ResourceTemplate: {ActionCreate, ActionDelete, ActionRead, ActionUpdate, ActionUse, ActionViewInsights}, ResourceUser: {ActionCreate, ActionDelete, ActionRead, ActionReadPersonal, ActionUpdate, ActionUpdatePersonal}, ResourceWorkspace: {ActionApplicationConnect, ActionCreate, ActionDelete, ActionRead, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate}, ResourceWorkspaceDormant: {ActionApplicationConnect, ActionCreate, ActionDelete, ActionRead, ActionSSH, ActionWorkspaceStart, ActionWorkspaceStop, ActionUpdate}, diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index 43f43c8ff7071..195c1e67ebe59 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -144,6 +144,7 @@ export const RBACResourceActions: Partial< delete: "delete a template", read: "read template", update: "update a template", + use: "use the template to create a workspace", view_insights: "view insights", }, user: { From a0f7f253d605d2fc19d10ca751a36378f83b0b79 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 8 Jan 2025 14:46:41 -0600 Subject: [PATCH 02/13] handle template acl perms --- .../migrations/000283_template_read_to_use.down.sql | 9 +++++++++ .../migrations/000283_template_read_to_use.up.sql | 12 ++++++++++++ coderd/rbac/policy/policy.go | 3 +-- coderd/rbac/roles.go | 4 ++-- enterprise/coderd/templates.go | 5 +++-- 5 files changed, 27 insertions(+), 6 deletions(-) create mode 100644 coderd/database/migrations/000283_template_read_to_use.down.sql create mode 100644 coderd/database/migrations/000283_template_read_to_use.up.sql diff --git a/coderd/database/migrations/000283_template_read_to_use.down.sql b/coderd/database/migrations/000283_template_read_to_use.down.sql new file mode 100644 index 0000000000000..019f54ce7ea7f --- /dev/null +++ b/coderd/database/migrations/000283_template_read_to_use.down.sql @@ -0,0 +1,9 @@ +-- With the "use" verb now existing for templates, we need to update the acl's to +-- include "use" where the permissions set ["read"] is present. +-- The other permission set is ["*"] which is unaffected. + +UPDATE + templates +SET + group_acl = replace(group_acl::text, '["read", "use"]', '["read"]')::jsonb, + user_acl = replace(user_acl::text, '["read", "use"]', '["read"]')::jsonb diff --git a/coderd/database/migrations/000283_template_read_to_use.up.sql b/coderd/database/migrations/000283_template_read_to_use.up.sql new file mode 100644 index 0000000000000..3729acc877e20 --- /dev/null +++ b/coderd/database/migrations/000283_template_read_to_use.up.sql @@ -0,0 +1,12 @@ +-- With the "use" verb now existing for templates, we need to update the acl's to +-- include "use" where the permissions set ["read"] is present. +-- The other permission set is ["*"] which is unaffected. + +UPDATE + templates +SET + -- Instead of trying to write a complicated SQL query to update the JSONB + -- object, a string replace is much simpler and easier to understand. + -- Both pieces of text are JSON arrays, so this safe to do. + group_acl = replace(group_acl::text, '["read"]', '["read", "use"]')::jsonb, + user_acl = replace(user_acl::text, '["read"]', '["read", "use"]')::jsonb diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index 948a307a9d296..97197234cea24 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -133,8 +133,7 @@ var RBACPermissions = map[string]PermissionDefinition{ }, "template": { Actions: map[Action]ActionDefinition{ - ActionCreate: actDef("create a template"), - // TODO: Create a use permission maybe? + ActionCreate: actDef("create a template"), ActionUse: actDef("use the template to create a workspace"), ActionRead: actDef("read template"), ActionUpdate: actDef("update a template"), diff --git a/coderd/rbac/roles.go b/coderd/rbac/roles.go index a57bd071a8052..944eb896bb491 100644 --- a/coderd/rbac/roles.go +++ b/coderd/rbac/roles.go @@ -318,7 +318,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { Identifier: RoleTemplateAdmin(), DisplayName: "Template Admin", Site: Permissions(map[string][]policy.Action{ - ResourceTemplate.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete, policy.ActionViewInsights}, + ResourceTemplate.Type: ResourceTemplate.AvailableActions(), // CRUD all files, even those they did not upload. ResourceFile.Type: {policy.ActionCreate, policy.ActionRead}, ResourceWorkspace.Type: {policy.ActionRead}, @@ -476,7 +476,7 @@ func ReloadBuiltinRoles(opts *RoleOptions) { Site: []Permission{}, Org: map[string][]Permission{ organizationID.String(): Permissions(map[string][]policy.Action{ - ResourceTemplate.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete, policy.ActionViewInsights}, + ResourceTemplate.Type: ResourceTemplate.AvailableActions(), ResourceFile.Type: {policy.ActionCreate, policy.ActionRead}, ResourceWorkspace.Type: {policy.ActionRead}, // Assigning template perms requires this permission. diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index 3cc82e6155d33..93217c8a44841 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -16,6 +16,7 @@ import ( "github.com/coder/coder/v2/coderd/httpapi" "github.com/coder/coder/v2/coderd/httpmw" "github.com/coder/coder/v2/coderd/rbac/policy" + "github.com/coder/coder/v2/coderd/util/slice" "github.com/coder/coder/v2/codersdk" ) @@ -326,7 +327,7 @@ func validateTemplateRole(role codersdk.TemplateRole) error { func convertToTemplateRole(actions []policy.Action) codersdk.TemplateRole { switch { - case len(actions) == 1 && actions[0] == policy.ActionRead: + case len(actions) == 2 && slice.SameElements(actions, []policy.Action{policy.ActionUse, policy.ActionRead}): return codersdk.TemplateRoleUse case len(actions) == 1 && actions[0] == policy.WildcardSymbol: return codersdk.TemplateRoleAdmin @@ -340,7 +341,7 @@ func convertSDKTemplateRole(role codersdk.TemplateRole) []policy.Action { case codersdk.TemplateRoleAdmin: return []policy.Action{policy.WildcardSymbol} case codersdk.TemplateRoleUse: - return []policy.Action{policy.ActionRead} + return []policy.Action{policy.ActionRead, policy.ActionUse} } return nil From d6740c088ae4aa690719ce565ba577c552fb0f37 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 8 Jan 2025 15:07:34 -0600 Subject: [PATCH 03/13] update rbac perm list test --- coderd/rbac/roles_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/coderd/rbac/roles_test.go b/coderd/rbac/roles_test.go index 0172439829063..f3b2979d6196c 100644 --- a/coderd/rbac/roles_test.go +++ b/coderd/rbac/roles_test.go @@ -232,6 +232,17 @@ func TestRolePermissions(t *testing.T) { false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgMemberMe}, }, }, + { + 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, groupMemberMe}, + false: {setOtherOrg, orgAuditor, orgUserAdmin, memberMe, userAdmin, orgMemberMe}, + }, + }, { Name: "Files", Actions: []policy.Action{policy.ActionCreate}, From 4234b830fc6f08c38aedbc10d061780a25af683d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 8 Jan 2025 15:14:46 -0600 Subject: [PATCH 04/13] check use perm on creating a workspace --- coderd/rbac/object_gen.go | 2 +- coderd/rbac/policy/policy.go | 2 +- coderd/workspaces.go | 12 ++++++++++++ site/src/api/rbacresourcesGenerated.ts | 2 +- 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/coderd/rbac/object_gen.go b/coderd/rbac/object_gen.go index 8076eb819be62..d2ff1d44921f6 100644 --- a/coderd/rbac/object_gen.go +++ b/coderd/rbac/object_gen.go @@ -256,7 +256,7 @@ var ( // - "ActionDelete" :: delete a template // - "ActionRead" :: read template // - "ActionUpdate" :: update a template - // - "ActionUse" :: use the template to create a workspace + // - "ActionUse" :: use the template to initially create a workspace, then workspace lifecycle permissions take over // - "ActionViewInsights" :: view insights ResourceTemplate = Object{ Type: "template", diff --git a/coderd/rbac/policy/policy.go b/coderd/rbac/policy/policy.go index 97197234cea24..1d07201292f78 100644 --- a/coderd/rbac/policy/policy.go +++ b/coderd/rbac/policy/policy.go @@ -134,7 +134,7 @@ var RBACPermissions = map[string]PermissionDefinition{ "template": { Actions: map[Action]ActionDefinition{ ActionCreate: actDef("create a template"), - ActionUse: actDef("use the template to create a workspace"), + ActionUse: actDef("use the template to initially create a workspace, then workspace lifecycle permissions take over"), ActionRead: actDef("read template"), ActionUpdate: actDef("update a template"), ActionDelete: actDef("delete a template"), diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 19fb1ec1ce810..336d0218c3d7a 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -515,6 +515,18 @@ func createWorkspace( return } + // The user also needs permission to use the template. At this point they have + // read perms, but not necessarily "use" + if !api.Authorize(r, policy.ActionUse, template) { + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ + Message: fmt.Sprintf("Unauthorized access to use the template %q.", template.Name), + Detail: "Although you are able to view the template, you are unable to create a workspace using it. " + + "Please contact an administrator about your permissions if you feel this is an error.", + Validations: nil, + }) + return + } + // Update audit log's organization auditReq.UpdateOrganizationID(template.OrganizationID) diff --git a/site/src/api/rbacresourcesGenerated.ts b/site/src/api/rbacresourcesGenerated.ts index 195c1e67ebe59..0b11eea022647 100644 --- a/site/src/api/rbacresourcesGenerated.ts +++ b/site/src/api/rbacresourcesGenerated.ts @@ -144,7 +144,7 @@ export const RBACResourceActions: Partial< delete: "delete a template", read: "read template", update: "update a template", - use: "use the template to create a workspace", + use: "use the template to initially create a workspace, then workspace lifecycle permissions take over", view_insights: "view insights", }, user: { From 0c08bc1ed6fc8d10736239cb14070902dcb677e5 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Wed, 8 Jan 2025 15:35:04 -0600 Subject: [PATCH 05/13] add unit test to verify auditor create workspace behavior --- enterprise/coderd/workspaces_test.go | 45 ++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 17685df83b387..d5db30a6f4962 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -192,6 +192,51 @@ func TestCreateWorkspace(t *testing.T) { require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) require.Contains(t, apiErr.Message, "doesn't exist") }) + + // Auditors cannot "use" templates, they can only read them. + t.Run("Auditor", func(t *testing.T) { + t.Parallel() + + owner, first := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + codersdk.FeatureMultipleOrganizations: 1, + }, + }, + }) + + // A member of the org as an auditor + auditor, _ := coderdtest.CreateAnotherUser(t, owner, first.OrganizationID, rbac.RoleAuditor()) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // Given: a template with a version without the "use" permission on everyone + version := coderdtest.CreateTemplateVersion(t, owner, first.OrganizationID, nil) + _ = coderdtest.AwaitTemplateVersionJobCompleted(t, owner, version.ID) + template := coderdtest.CreateTemplate(t, owner, first.OrganizationID, version.ID) + err := owner.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{ + UserPerms: nil, + GroupPerms: map[string]codersdk.TemplateRole{ + first.OrganizationID.String(): codersdk.TemplateRoleDeleted, + }, + }) + require.NoError(t, err) + + _, err = auditor.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateID: template.ID, + Name: "workspace", + }) + require.Error(t, err) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusForbidden, apiErr.StatusCode()) + require.Contains(t, apiErr.Message, "Unauthorized access to use the template") + }) } func TestCreateUserWorkspace(t *testing.T) { From bdb29b8903c16bf7d87de4f53b93be354de5736a Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 9 Jan 2025 11:34:45 -0600 Subject: [PATCH 06/13] chore: fix default template policy actions --- coderd/database/db2sdk/db2sdk.go | 11 +++++++++++ coderd/database/dbgen/dbgen.go | 5 +++-- coderd/templates.go | 3 ++- enterprise/coderd/templates.go | 17 +++-------------- enterprise/coderd/workspaces_test.go | 2 ++ 5 files changed, 21 insertions(+), 17 deletions(-) diff --git a/coderd/database/db2sdk/db2sdk.go b/coderd/database/db2sdk/db2sdk.go index aabebcd14b7ac..8d2a75960bd0e 100644 --- a/coderd/database/db2sdk/db2sdk.go +++ b/coderd/database/db2sdk/db2sdk.go @@ -17,6 +17,7 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/rbac" + "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/render" "github.com/coder/coder/v2/coderd/workspaceapps/appurl" "github.com/coder/coder/v2/codersdk" @@ -694,3 +695,13 @@ func MatchedProvisioners(provisionerDaemons []database.ProvisionerDaemon, now ti } return matched } + +func TemplateRoleActions(role codersdk.TemplateRole) []policy.Action { + switch role { + case codersdk.TemplateRoleAdmin: + return []policy.Action{policy.WildcardSymbol} + case codersdk.TemplateRoleUse: + return []policy.Action{policy.ActionRead, policy.ActionUse} + } + return []policy.Action{} +} diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index dd6a3a2cc1490..bf972e78c72ab 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -20,12 +20,13 @@ import ( "golang.org/x/xerrors" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbtime" "github.com/coder/coder/v2/coderd/database/provisionerjobs" "github.com/coder/coder/v2/coderd/database/pubsub" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/rbac/policy" + "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/cryptorand" "github.com/coder/coder/v2/testutil" ) @@ -75,7 +76,7 @@ func Template(t testing.TB, db database.Store, seed database.Template) database. if seed.GroupACL == nil { // By default, all users in the organization can read the template. seed.GroupACL = database.TemplateACL{ - seed.OrganizationID.String(): []policy.Action{policy.ActionRead}, + seed.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse), } } if seed.UserACL == nil { diff --git a/coderd/templates.go b/coderd/templates.go index 4280c25607ab7..f5ff871650823 100644 --- a/coderd/templates.go +++ b/coderd/templates.go @@ -14,6 +14,7 @@ import ( "golang.org/x/xerrors" "cdr.dev/slog" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/database" @@ -382,7 +383,7 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque if !createTemplate.DisableEveryoneGroupAccess { // The organization ID is used as the group ID for the everyone group // in this organization. - defaultsGroups[organization.ID.String()] = []policy.Action{policy.ActionRead} + defaultsGroups[organization.ID.String()] = db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse) } err = api.Database.InTx(func(tx database.Store) error { now := dbtime.Now() diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index 93217c8a44841..d823f563df385 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -223,7 +223,7 @@ func (api *API) patchTemplateACL(rw http.ResponseWriter, r *http.Request) { delete(template.UserACL, id) continue } - template.UserACL[id] = convertSDKTemplateRole(role) + template.UserACL[id] = db2sdk.TemplateRoleActions(role) } } @@ -235,7 +235,7 @@ func (api *API) patchTemplateACL(rw http.ResponseWriter, r *http.Request) { delete(template.GroupACL, id) continue } - template.GroupACL[id] = convertSDKTemplateRole(role) + template.GroupACL[id] = db2sdk.TemplateRoleActions(role) } } @@ -317,7 +317,7 @@ func convertTemplateUsers(tus []database.TemplateUser, orgIDsByUserIDs map[uuid. } func validateTemplateRole(role codersdk.TemplateRole) error { - actions := convertSDKTemplateRole(role) + actions := db2sdk.TemplateRoleActions(role) if actions == nil && role != codersdk.TemplateRoleDeleted { return xerrors.Errorf("role %q is not a valid Template role", role) } @@ -336,17 +336,6 @@ func convertToTemplateRole(actions []policy.Action) codersdk.TemplateRole { return "" } -func convertSDKTemplateRole(role codersdk.TemplateRole) []policy.Action { - switch role { - case codersdk.TemplateRoleAdmin: - return []policy.Action{policy.WildcardSymbol} - case codersdk.TemplateRoleUse: - return []policy.Action{policy.ActionRead, policy.ActionUse} - } - - return nil -} - // TODO move to api.RequireFeatureMW when we are OK with changing the behavior. func (api *API) templateRBACEnabledMW(next http.Handler) http.Handler { return api.RequireFeatureMW(codersdk.FeatureTemplateRBAC)(next) diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index d5db30a6f4962..07b3a0629aa17 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -219,6 +219,8 @@ func TestCreateWorkspace(t *testing.T) { version := coderdtest.CreateTemplateVersion(t, owner, first.OrganizationID, nil) _ = coderdtest.AwaitTemplateVersionJobCompleted(t, owner, version.ID) template := coderdtest.CreateTemplate(t, owner, first.OrganizationID, version.ID) + + //nolint:gocritic // This should be run as the owner user. err := owner.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{ UserPerms: nil, GroupPerms: map[string]codersdk.TemplateRole{ From ed6de5e09de7cc414ea8185e9278fcd6680a8bc5 Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 9 Jan 2025 11:53:24 -0600 Subject: [PATCH 07/13] fix template perms in unit test --- coderd/insights_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index 43ef04435c218..d08477261a93d 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -23,6 +23,7 @@ import ( agentproto "github.com/coder/coder/v2/agent/proto" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/db2sdk" "github.com/coder/coder/v2/coderd/database/dbauthz" "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/database/dbrollup" @@ -675,7 +676,7 @@ func TestTemplateInsights_Golden(t *testing.T) { OrganizationID: firstUser.OrganizationID, CreatedBy: firstUser.UserID, GroupACL: database.TemplateACL{ - firstUser.OrganizationID.String(): []policy.Action{policy.ActionRead}, + firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse), }, }) err := db.UpdateTemplateVersionByID(context.Background(), database.UpdateTemplateVersionByIDParams{ From 8dea6c0bdb987db344bfd012e3594974831e363c Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 9 Jan 2025 12:03:09 -0600 Subject: [PATCH 08/13] use len check over nil check --- .../database/migrations/000283_template_read_to_use.down.sql | 4 ---- enterprise/coderd/templates.go | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/coderd/database/migrations/000283_template_read_to_use.down.sql b/coderd/database/migrations/000283_template_read_to_use.down.sql index 019f54ce7ea7f..7ecca75ce15b8 100644 --- a/coderd/database/migrations/000283_template_read_to_use.down.sql +++ b/coderd/database/migrations/000283_template_read_to_use.down.sql @@ -1,7 +1,3 @@ --- With the "use" verb now existing for templates, we need to update the acl's to --- include "use" where the permissions set ["read"] is present. --- The other permission set is ["*"] which is unaffected. - UPDATE templates SET diff --git a/enterprise/coderd/templates.go b/enterprise/coderd/templates.go index d823f563df385..37c0151749196 100644 --- a/enterprise/coderd/templates.go +++ b/enterprise/coderd/templates.go @@ -318,7 +318,7 @@ func convertTemplateUsers(tus []database.TemplateUser, orgIDsByUserIDs map[uuid. func validateTemplateRole(role codersdk.TemplateRole) error { actions := db2sdk.TemplateRoleActions(role) - if actions == nil && role != codersdk.TemplateRoleDeleted { + if len(actions) == 0 && role != codersdk.TemplateRoleDeleted { return xerrors.Errorf("role %q is not a valid Template role", role) } From 4586398295e1636c592deea62cf7960ef38ec66d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Thu, 9 Jan 2025 12:04:21 -0600 Subject: [PATCH 09/13] fix test template acl perms --- coderd/insights_test.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/coderd/insights_test.go b/coderd/insights_test.go index d08477261a93d..53f70c66df70d 100644 --- a/coderd/insights_test.go +++ b/coderd/insights_test.go @@ -29,7 +29,6 @@ import ( "github.com/coder/coder/v2/coderd/database/dbrollup" "github.com/coder/coder/v2/coderd/database/dbtestutil" "github.com/coder/coder/v2/coderd/rbac" - "github.com/coder/coder/v2/coderd/rbac/policy" "github.com/coder/coder/v2/coderd/workspaceapps" "github.com/coder/coder/v2/coderd/workspacestats" "github.com/coder/coder/v2/codersdk" @@ -1574,7 +1573,7 @@ func TestUserActivityInsights_Golden(t *testing.T) { OrganizationID: firstUser.OrganizationID, CreatedBy: firstUser.UserID, GroupACL: database.TemplateACL{ - firstUser.OrganizationID.String(): []policy.Action{policy.ActionRead}, + firstUser.OrganizationID.String(): db2sdk.TemplateRoleActions(codersdk.TemplateRoleUse), }, }) err := db.UpdateTemplateVersionByID(context.Background(), database.UpdateTemplateVersionByIDParams{ From 7df41c5cc78b81f668a13bb3cb82475f2f93a32f Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 10 Jan 2025 09:13:46 -0600 Subject: [PATCH 10/13] chore: enforce tpl use in dbauthz --- coderd/database/dbauthz/dbauthz.go | 8 ++++++++ coderd/workspaces.go | 24 ++++++++++++------------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/coderd/database/dbauthz/dbauthz.go b/coderd/database/dbauthz/dbauthz.go index a4c3208aa5e6d..d3bbcf4ce4c3c 100644 --- a/coderd/database/dbauthz/dbauthz.go +++ b/coderd/database/dbauthz/dbauthz.go @@ -3165,6 +3165,14 @@ func (q *querier) InsertUserLink(ctx context.Context, arg database.InsertUserLin func (q *querier) InsertWorkspace(ctx context.Context, arg database.InsertWorkspaceParams) (database.WorkspaceTable, error) { obj := rbac.ResourceWorkspace.WithOwner(arg.OwnerID.String()).InOrg(arg.OrganizationID) + tpl, err := q.GetTemplateByID(ctx, arg.TemplateID) + if err != nil { + return database.WorkspaceTable{}, xerrors.Errorf("verify template by id: %w", err) + } + if err := q.authorizeContext(ctx, policy.ActionUse, tpl); err != nil { + return database.WorkspaceTable{}, xerrors.Errorf("use template for workspace: %w", err) + } + return insert(q.log, q.auth, obj, q.db.InsertWorkspace)(ctx, arg) } diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 336d0218c3d7a..1775e474d03ca 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -515,18 +515,6 @@ func createWorkspace( return } - // The user also needs permission to use the template. At this point they have - // read perms, but not necessarily "use" - if !api.Authorize(r, policy.ActionUse, template) { - httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ - Message: fmt.Sprintf("Unauthorized access to use the template %q.", template.Name), - Detail: "Although you are able to view the template, you are unable to create a workspace using it. " + - "Please contact an administrator about your permissions if you feel this is an error.", - Validations: nil, - }) - return - } - // Update audit log's organization auditReq.UpdateOrganizationID(template.OrganizationID) @@ -537,6 +525,18 @@ func createWorkspace( httpapi.ResourceNotFound(rw) return } + // The user also needs permission to use the template. At this point they have + // read perms, but not necessarily "use". This is also checked in `db.InsertWorkspace`. + // Doing this up front can save some work below if the user doesn't have permission. + if !api.Authorize(r, policy.ActionUse, template) { + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ + Message: fmt.Sprintf("Unauthorized access to use the template %q.", template.Name), + Detail: "Although you are able to view the template, you are unable to create a workspace using it. " + + "Please contact an administrator about your permissions if you feel this is an error.", + Validations: nil, + }) + return + } templateAccessControl := (*(api.AccessControlStore.Load())).GetTemplateAccessControl(template) if templateAccessControl.IsDeprecated() { From 36d329c74271822b701a1b20ceec9a35d876f26d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 13 Jan 2025 06:42:37 -0600 Subject: [PATCH 11/13] fix dbauthz test --- coderd/database/dbauthz/dbauthz_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/coderd/database/dbauthz/dbauthz_test.go b/coderd/database/dbauthz/dbauthz_test.go index 78500792933b5..fc642d6486bb7 100644 --- a/coderd/database/dbauthz/dbauthz_test.go +++ b/coderd/database/dbauthz/dbauthz_test.go @@ -2458,7 +2458,7 @@ func (s *MethodTestSuite) TestWorkspace() { OrganizationID: o.ID, AutomaticUpdates: database.AutomaticUpdatesNever, TemplateID: tpl.ID, - }).Asserts(rbac.ResourceWorkspace.WithOwner(u.ID.String()).InOrg(o.ID), policy.ActionCreate) + }).Asserts(tpl, policy.ActionRead, tpl, policy.ActionUse, rbac.ResourceWorkspace.WithOwner(u.ID.String()).InOrg(o.ID), policy.ActionCreate) })) s.Run("Start/InsertWorkspaceBuild", s.Subtest(func(db database.Store, check *expects) { u := dbgen.User(s.T(), db, database.User{}) From 9b04ab6555465bb59a6d457e40a0b5240b246bea Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Mon, 13 Jan 2025 06:45:15 -0600 Subject: [PATCH 12/13] bump migration number --- ..._read_to_use.down.sql => 000284_template_read_to_use.down.sql} | 0 ...late_read_to_use.up.sql => 000284_template_read_to_use.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000283_template_read_to_use.down.sql => 000284_template_read_to_use.down.sql} (100%) rename coderd/database/migrations/{000283_template_read_to_use.up.sql => 000284_template_read_to_use.up.sql} (100%) diff --git a/coderd/database/migrations/000283_template_read_to_use.down.sql b/coderd/database/migrations/000284_template_read_to_use.down.sql similarity index 100% rename from coderd/database/migrations/000283_template_read_to_use.down.sql rename to coderd/database/migrations/000284_template_read_to_use.down.sql diff --git a/coderd/database/migrations/000283_template_read_to_use.up.sql b/coderd/database/migrations/000284_template_read_to_use.up.sql similarity index 100% rename from coderd/database/migrations/000283_template_read_to_use.up.sql rename to coderd/database/migrations/000284_template_read_to_use.up.sql From 389768b48f8d0ab9cbd28bc66f7bae308011062d Mon Sep 17 00:00:00 2001 From: Steven Masley Date: Fri, 17 Jan 2025 09:47:39 -0600 Subject: [PATCH 13/13] migration bump --- ..._read_to_use.down.sql => 000287_template_read_to_use.down.sql} | 0 ...late_read_to_use.up.sql => 000287_template_read_to_use.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000284_template_read_to_use.down.sql => 000287_template_read_to_use.down.sql} (100%) rename coderd/database/migrations/{000284_template_read_to_use.up.sql => 000287_template_read_to_use.up.sql} (100%) diff --git a/coderd/database/migrations/000284_template_read_to_use.down.sql b/coderd/database/migrations/000287_template_read_to_use.down.sql similarity index 100% rename from coderd/database/migrations/000284_template_read_to_use.down.sql rename to coderd/database/migrations/000287_template_read_to_use.down.sql diff --git a/coderd/database/migrations/000284_template_read_to_use.up.sql b/coderd/database/migrations/000287_template_read_to_use.up.sql similarity index 100% rename from coderd/database/migrations/000284_template_read_to_use.up.sql rename to coderd/database/migrations/000287_template_read_to_use.up.sql