10000 feat: allow TemplateAdmin to delete prebuilds via auth layer by ssncferreira · Pull Request #18333 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat: allow TemplateAdmin to delete prebuilds via auth layer #18333

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat: POC for allowing TemplateAdmin to delete prebuild workspaces vi…
…a auth layer
  • Loading branch information
ssncferreira committed Jun 11, 2025
commit 2ba15c516126482aedf9ce1ad87ce757e39adb61
2 changes: 2 additions & 0 deletions coderd/apidoc/docs.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions coderd/apidoc/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

92 changes: 55 additions & 37 deletions coderd/database/dbauthz/dbauthz.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ var (
policy.ActionCreate, policy.ActionDelete, policy.ActionRead, policy.ActionUpdate,
policy.ActionWorkspaceStart, policy.ActionWorkspaceStop,
},
rbac.ResourcePrebuiltWorkspace.Type: {
policy.ActionRead, policy.ActionUpdate, policy.ActionDelete,
},
// Should be able to add the prebuilds system user as a member to any organization that needs prebuilds.
rbac.ResourceOrganizationMember.Type: {
policy.ActionCreate,
Expand Down Expand Up @@ -527,9 +530,9 @@ func As(ctx context.Context, actor rbac.Subject) context.Context {
// running the insertFunc. The insertFunc is expected to return the object that
// was inserted.
func insert[
ObjectType any,
ArgumentType any,
Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ObjectType any,
ArgumentType any,
Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -540,9 +543,9 @@ func insert[
}

func insertWithAction[
ObjectType any,
ArgumentType any,
Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ObjectType any,
ArgumentType any,
Insert func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -569,10 +572,10 @@ func insertWithAction[
}

func deleteQ[
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Delete func(ctx context.Context, arg ArgumentType) error,
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Delete func(ctx context.Context, arg ArgumentType) error,
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -584,10 +587,10 @@ func deleteQ[
}

func updateWithReturn[
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
UpdateQuery func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -598,10 +601,10 @@ func updateWithReturn[
}

func update[
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Exec func(ctx context.Context, arg ArgumentType) error,
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Exec func(ctx context.Context, arg ArgumentType) error,
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -619,9 +622,9 @@ func update[
// user cannot read the resource. This is because the resource details are
// required to run a proper authorization check.
func fetchWithAction[
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand Down Expand Up @@ -652,9 +655,9 @@ func fetchWithAction[
}

func fetch[
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -667,10 +670,10 @@ func fetch[
// from SQL 'exec' functions which only return an error.
// See fetchAndQuery for more information.
func fetchAndExec[
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Exec func(ctx context.Context, arg ArgumentType) error,
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Exec func(ctx context.Context, arg ArgumentType) error,
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand All @@ -693,10 +696,10 @@ func fetchAndExec[
// **before** the query runs. The returns from the fetch are only used to
// assert rbac. The final return of this function comes from the Query function.
func fetchAndQuery[
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Query func(ctx context.Context, arg ArgumentType) (ObjectType, error),
ObjectType rbac.Objecter,
ArgumentType any,
Fetch func(ctx context.Context, arg ArgumentType) (ObjectType, error),
Query func(ctx context.Context, arg ArgumentType) (ObjectType, error),
](
logger slog.Logger,
authorizer rbac.Authorizer,
Expand Down Expand Up @@ -730,9 +733,9 @@ func fetchAndQuery[
// fetchWithPostFilter is like fetch, but works with lists of objects.
// SQL filters are much more optimal.
func fetchWithPostFilter[
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error),
ArgumentType any,
ObjectType rbac.Objecter,
DatabaseFunc func(ctx context.Context, arg ArgumentType) ([]ObjectType, error),
](
authorizer rbac.Authorizer,
action policy.Action,
Expand Down Expand Up @@ -3909,7 +3912,14 @@ func (q *querier) InsertWorkspaceBuild(ctx context.Context, arg database.InsertW
action = policy.ActionWorkspaceStop
}

if err = q.authorizeContext(ctx, action, w); err != nil {
if action == policy.ActionDelete && w.IsPrebuild() {
if err := q.authorizeContext(ctx, action, w.PrebuildRBAC()); err != nil {
// Fallback to normal workspace auth check
if err = q.authorizeContext(ctx, action, w); err != nil {
return xerrors.Errorf("authorize context: %w", err)
}
}
} else if err = q.authorizeContext(ctx, action, w); err != nil {
return xerrors.Errorf("authorize context: %w", err)
}

Expand Down Expand Up @@ -3949,7 +3959,15 @@ func (q *querier) InsertWorkspaceBuildParameters(ctx context.Context, arg databa
return err
}

err = q.authorizeContext(ctx, policy.ActionUpdate, workspace)
if workspace.IsPrebuild() {
err = q.authorizeContext(ctx, policy.ActionUpdate, workspace.PrebuildRBAC())
// Fallback to normal workspace auth check
if err != nil {
err = q.authorizeContext(ctx, policy.ActionUpdate, workspace)
}
} else {
err = q.authorizeContext(ctx, policy.ActionUpdate, workspace)
}
if err != nil {
return err
}
Expand Down
17 changes: 17 additions & 0 deletions coderd/database/modelmethods.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,9 +226,26 @@ func (w Workspace) WorkspaceTable() WorkspaceTable {
}

func (w Workspace) RBACObject() rbac.Object {
// if w.IsPrebuild() {
// return w.PrebuildRBAC()
//}
return w.WorkspaceTable().RBACObject()
}

func (w Workspace) IsPrebuild() bool {
// TODO: avoid import cycle
return w.OwnerID == uuid.MustParse("c42fdf75-3097-471c-8c33-fb52454d81c0")
}

func (w Workspace) PrebuildRBAC() rbac.Object {
if w.IsPrebuild() {
return rbac.ResourcePrebuiltWorkspace.WithID(w.ID).
InOrg(w.OrganizationID).
WithOwner(w.OwnerID.String())
}
return w.RBACObject()
}

func (w WorkspaceTable) RBACObject() rbac.Object {
if w.DormantAt.Valid {
return w.DormantRBAC()
Expand Down
10 changes: 10 additions & 0 deletions coderd/rbac/object_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions coderd/rbac/policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ var RBACPermissions = map[string]PermissionDefinition{
"workspace_dormant": {
Actions: workspaceActions,
},
"prebuilt_workspace": {
Actions: map[Action]ActionDefinition{
ActionRead: actDef("read prebuilt workspace"),
ActionUpdate: actDef("update prebuilt workspace"),
ActionDelete: actDef("delete prebuilt workspace"),
},
},
"workspace_proxy": {
Actions: map[Action]ActionDefinition{
ActionCreate: actDef("create a workspace proxy"),
Expand Down
12 changes: 7 additions & 5 deletions coderd/rbac/roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,9 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
ResourceAssignOrgRole.Type: {policy.ActionRead},
ResourceTemplate.Type: ResourceTemplate.AvailableActions(),
// CRUD all files, even those they did not upload.
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourcePrebuiltWorkspace.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
// CRUD to provisioner daemons for now.
ResourceProvisionerDaemon.Type: {policy.ActionCreate, policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
// Needs to read all organizations since
Expand Down Expand Up @@ -493,9 +494,10 @@ func ReloadBuiltinRoles(opts *RoleOptions) {
Site: []Permission{},
Org: map[string][]Permission{
organizationID.String(): Permissions(map[string][]policy.Action{
ResourceTemplate.Type: ResourceTemplate.AvailableActions(),
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourceTemplate.Type: ResourceTemplate.AvailableActions(),
ResourceFile.Type: {policy.ActionCreate, policy.ActionRead},
ResourceWorkspace.Type: {policy.ActionRead},
ResourcePrebuiltWorkspace.Type: {policy.ActionRead, policy.ActionUpdate, policy.ActionDelete},
// Assigning template perms requires this permission.
ResourceOrganization.Type: {policy.ActionRead},
ResourceOrganizationMember.Type: {policy.ActionRead},
Expand Down
10 changes: 10 additions & 0 deletions coderd/workspacebuilds.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,16 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
ctx,
tx,
func(action policy.Action, object rbac.Objecter) bool {
if object.RBACObject().Type == rbac.ResourceWorkspace.Type && action == policy.ActionDelete {
workspaceObj, ok := object.(database.Workspace)
if ok {
prebuild := workspaceObj.PrebuildRBAC()
// Fallback to normal workspace auth check
if auth := api.Authorize(r, action, prebuild); auth {
return auth
}
}
}
return api.Authorize(r, action, object)
},
audit.WorkspaceBuildBaggageFromRequest(r),
Expand Down
2 changes: 2 additions & 0 deletions codersdk/rbacresources_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions docs/reference/api/members.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions docs/reference/api/schemas.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading
0