Skip to content

Commit 3d30c8d

Browse files
authored
chore: protect reserved builtin rolenames (#13571)
Conflicting built-in and database role names makes it hard to disambiguate
1 parent 7d51515 commit 3d30c8d

File tree

3 files changed

+46
-0
lines changed

3 files changed

+46
-0
lines changed

coderd/rbac/roles.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,13 @@ type RoleOptions struct {
195195
NoOwnerWorkspaceExec bool
196196
}
197197

198+
// ReservedRoleName exists because the database should only allow unique role
199+
// names, but some roles are built in. So these names are reserved
200+
func ReservedRoleName(name string) bool {
201+
_, ok := builtInRoles[name]
202+
return ok
203+
}
204+
198205
// ReloadBuiltinRoles loads the static roles into the builtInRoles map.
199206
// This can be called again with a different config to change the behavior.
200207
//

enterprise/coderd/roles.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"github.com/coder/coder/v2/coderd/database"
1212
"github.com/coder/coder/v2/coderd/database/db2sdk"
1313
"github.com/coder/coder/v2/coderd/httpapi"
14+
"github.com/coder/coder/v2/coderd/rbac"
1415
"github.com/coder/coder/v2/coderd/rbac/policy"
1516
"github.com/coder/coder/v2/codersdk"
1617
)
@@ -41,6 +42,16 @@ func (h enterpriseCustomRoleHandler) PatchOrganizationRole(ctx context.Context,
4142
)
4243
defer commitAudit()
4344

45+
// This check is not ideal, but we cannot enforce a unique role name in the db against
46+
// the built-in role names.
47+
if rbac.ReservedRoleName(role.Name) {
48+
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
49+
Message: "Reserved role name",
50+
Detail: fmt.Sprintf("%q is a reserved role name, and not allowed to be used", role.Name),
51+
})
52+
return codersdk.Role{}, false
53+
}
54+
4455
if err := httpapi.NameValid(role.Name); err != nil {
4556
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
4657
Message: "Invalid role name",

enterprise/coderd/roles_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,34 @@ func TestCustomOrganizationRole(t *testing.T) {
210210
require.ErrorContains(t, err, "Validation")
211211
})
212212

213+
t.Run("ReservedName", func(t *testing.T) {
214+
t.Parallel()
215+
dv := coderdtest.DeploymentValues(t)
216+
dv.Experiments = []string{string(codersdk.ExperimentCustomRoles)}
217+
owner, first := coderdenttest.New(t, &coderdenttest.Options{
218+
Options: &coderdtest.Options{
219+
DeploymentValues: dv,
220+
},
221+
LicenseOptions: &coderdenttest.LicenseOptions{
222+
Features: license.Features{
223+
codersdk.FeatureCustomRoles: 1,
224+
},
225+
},
226+
})
227+
228+
ctx := testutil.Context(t, testutil.WaitMedium)
229+
230+
//nolint:gocritic // owner is required for this
231+
_, err := owner.PatchOrganizationRole(ctx, first.OrganizationID, codersdk.Role{
232+
Name: "owner", // Reserved
233+
DisplayName: "Testing Purposes",
234+
SitePermissions: nil,
235+
OrganizationPermissions: nil,
236+
UserPermissions: nil,
237+
})
238+
require.ErrorContains(t, err, "Reserved")
239+
})
240+
213241
t.Run("MismatchedOrganizations", func(t *testing.T) {
214242
t.Parallel()
215243
dv := coderdtest.DeploymentValues(t)

0 commit comments

Comments
 (0)