Skip to content

Commit 60059ba

Browse files
committed
feat: deny custom state in build for regular users
1 parent 86f5808 commit 60059ba

File tree

2 files changed

+66
-0
lines changed

2 files changed

+66
-0
lines changed

coderd/workspacebuilds.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,6 +372,17 @@ func (api *API) postWorkspaceBuilds(rw http.ResponseWriter, r *http.Request) {
372372
return
373373
}
374374

375+
// If custom state, deny request since user could be orphaning their
376+
// cloud resources.
377+
if createBuild.ProvisionerState != nil {
378+
if !api.Authorize(r, rbac.ActionUpdate, template.RBACObject()) {
379+
httpapi.Write(rw, http.StatusForbidden, codersdk.Response{
380+
Message: "Only template managers may provide custom state",
381+
})
382+
return
383+
}
384+
}
385+
375386
// Store prior build number to compute new build number
376387
var priorBuildNum int32
377388
priorHistory, err := api.Database.GetLatestWorkspaceBuildByWorkspaceID(r.Context(), workspace.ID)

coderd/workspacebuilds_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd_test
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"net/http"
78
"strconv"
@@ -164,6 +165,60 @@ func TestWorkspaceBuilds(t *testing.T) {
164165
require.NoError(t, err)
165166
})
166167

168+
t.Run("OrphanNotOwner", func(t *testing.T) {
169+
t.Parallel()
170+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
171+
first := coderdtest.CreateFirstUser(t, client)
172+
173+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
174+
defer cancel()
175+
176+
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
177+
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
178+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
179+
180+
regularUser := coderdtest.CreateAnotherUser(t, client, first.OrganizationID)
181+
182+
workspace := coderdtest.CreateWorkspace(t, regularUser, first.OrganizationID, template.ID)
183+
coderdtest.AwaitWorkspaceBuildJob(t, regularUser, workspace.LatestBuild.ID)
184+
185+
_, err := regularUser.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
186+
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
187+
Transition: workspace.LatestBuild.Transition,
188+
ProvisionerState: []byte(" "),
189+
})
190+
require.Error(t, err)
191+
192+
var cerr *codersdk.Error
193+
require.True(t, errors.As(err, &cerr))
194+
195+
code := cerr.StatusCode()
196+
require.Equal(t, http.StatusForbidden, code, "unexpected status %s", http.StatusText(code))
197+
})
198+
199+
t.Run("Orphan", func(t *testing.T) {
200+
t.Parallel()
201+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})
202+
first := coderdtest.CreateFirstUser(t, client)
203+
204+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
205+
defer cancel()
206+
207+
version := coderdtest.CreateTemplateVersion(t, client, first.OrganizationID, nil)
208+
template := coderdtest.CreateTemplate(t, client, first.OrganizationID, version.ID)
209+
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
210+
211+
workspace := coderdtest.CreateWorkspace(t, client, first.OrganizationID, template.ID)
212+
coderdtest.AwaitWorkspaceBuildJob(t, client, workspace.LatestBuild.ID)
213+
214+
_, err := client.CreateWorkspaceBuild(ctx, workspace.ID, codersdk.CreateWorkspaceBuildRequest{
215+
TemplateVersionID: workspace.LatestBuild.TemplateVersionID,
216+
Transition: workspace.LatestBuild.Transition,
217+
ProvisionerState: []byte(" "),
218+
})
219+
require.Nil(t, err)
220+
})
221+
167222
t.Run("PaginateNonExistentRow", func(t *testing.T) {
168223
t.Parallel()
169224
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerD: true})

0 commit comments

Comments
 (0)