Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 29bac36

Browse files
authored
feat: add workspace auditing (#3966)
1 parent 442df9e commit 29bac36

File tree

4 files changed

+91
-15
lines changed

4 files changed

+91
-15
lines changed

coderd/users_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,8 @@ func TestPostUsers(t *testing.T) {
391391
Password: "testing",
392392
})
393393
require.NoError(t, err)
394-
assert.Len(t, auditor.AuditLogs, 1)
394+
395+
require.Len(t, auditor.AuditLogs, 1)
395396
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[0].Action)
396397
})
397398
}

coderd/workspaces.go

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"cdr.dev/slog"
2424

25+
"github.com/coder/coder/coderd/audit"
2526
"github.com/coder/coder/coderd/autobuild/schedule"
2627
"github.com/coder/coder/coderd/database"
2728
"github.com/coder/coder/coderd/httpapi"
@@ -248,8 +249,18 @@ func (api *API) workspaceByOwnerAndName(rw http.ResponseWriter, r *http.Request)
248249

249250
// Create a new workspace for the currently authenticated user.
250251
func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Request) {
251-
organization := httpmw.OrganizationParam(r)
252-
apiKey := httpmw.APIKey(r)
252+
var (
253+
organization = httpmw.OrganizationParam(r)
254+
apiKey = httpmw.APIKey(r)
255+
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
256+
Features: api.FeaturesService,
257+
Log: api.Logger,
258+
Request: r,
259+
Action: database.AuditActionCreate,
260+
})
261+
)
262+
defer commitAudit()
263+
253264
if !api.Authorize(r, rbac.ActionCreate,
254265
rbac.ResourceWorkspace.InOrg(organization.ID).WithOwner(apiKey.UserID.String())) {
255266
httpapi.ResourceNotFound(rw)
@@ -325,7 +336,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
325336
})
326337
return
327338
}
328-
if !errors.Is(err, sql.ErrNoRows) {
339+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
329340
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
330341
Message: fmt.Sprintf("Internal error fetching workspace by name %q.", createWorkspace.Name),
331342
Detail: err.Error(),
@@ -457,6 +468,8 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
457468
})
458469
return
459470
}
471+
aReq.New = workspace
472+
460473
users, err := api.Database.GetUsersByIDs(r.Context(), []uuid.UUID{apiKey.UserID, workspaceBuild.InitiatorID})
461474
if err != nil {
462475
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
@@ -476,7 +489,18 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
476489
}
477490

478491
func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
479-
workspace := httpmw.WorkspaceParam(r)
492+
var (
493+
workspace = httpmw.WorkspaceParam(r)
494+
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
495+
Features: api.FeaturesService,
496+
Log: api.Logger,
497+
Request: r,
498+
Action: database.AuditActionWrite,
499+
})
500+
)
501+
defer commitAudit()
502+
aReq.Old = workspace
503+
480504
if !api.Authorize(r, rbac.ActionUpdate, workspace) {
481505
httpapi.ResourceNotFound(rw)
482506
return
@@ -488,18 +512,20 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
488512
}
489513

490514
if req.Name == "" || req.Name == workspace.Name {
515+
aReq.New = workspace
491516
// Nothing changed, optionally this could be an error.
492517
rw.WriteHeader(http.StatusNoContent)
493518
return
494519
}
520+
495521
// The reason we double check here is in case more fields can be
496522
// patched in the future, it's enough if one changes.
497523
name := workspace.Name
498524
if req.Name != "" || req.Name != workspace.Name {
499525
name = req.Name
500526
}
501527

502-
_, err := api.Database.UpdateWorkspace(r.Context(), database.UpdateWorkspaceParams{
528+
newWorkspace, err := api.Database.UpdateWorkspace(r.Context(), database.UpdateWorkspaceParams{
503529
ID: workspace.ID,
504530
Name: name,
505531
})
@@ -534,11 +560,23 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
534560
return
535561
}
536562

563+
aReq.New = newWorkspace
537564
rw.WriteHeader(http.StatusNoContent)
538565
}
539566

540567
func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
541-
workspace := httpmw.WorkspaceParam(r)
568+
var (
569+
workspace = httpmw.WorkspaceParam(r)
570+
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
571+
Features: api.FeaturesService,
572+
Log: api.Logger,
573+
Request: r,
574+
Action: database.AuditActionWrite,
575+
})
576+
)
577+
defer commitAudit()
578+
aReq.Old = workspace
579+
542580
if !api.Authorize(r, rbac.ActionUpdate, workspace) {
543581
httpapi.ResourceNotFound(rw)
544582
return
@@ -578,10 +616,26 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
578616
})
579617
return
580618
}
619+
620+
newWorkspace := workspace
621+
newWorkspace.AutostartSchedule = dbSched
622+
aReq.New = newWorkspace
623+
624+
rw.WriteHeader(http.StatusNoContent)
581625
}
582626

583627
func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
584-
workspace := httpmw.WorkspaceParam(r)
628+
var (
629+
workspace = httpmw.WorkspaceParam(r)
630+
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
631+
Features: api.FeaturesService,
632+
Log: api.Logger,
633+
Request: r,
634+
Action: database.AuditActionWrite,
635+
})
636+
)
637+
defer commitAudit()
638+
585639
if !api.Authorize(r, rbac.ActionUpdate, workspace) {
586640
httpapi.ResourceNotFound(rw)
587641
return
@@ -592,6 +646,8 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
592646
return
593647
}
594648

649+
var dbTTL sql.NullInt64
650+
595651
err := api.Database.InTx(func(s database.Store) error {
596652
template, err := s.GetTemplateByID(r.Context(), workspace.TemplateID)
597653
if err != nil {
@@ -601,7 +657,7 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
601657
return xerrors.Errorf("fetch workspace template: %w", err)
602658
}
603659

604-
dbTTL, err := validWorkspaceTTLMillis(req.TTLMillis, time.Duration(template.MaxTtl))
660+
dbTTL, err = validWorkspaceTTLMillis(req.TTLMillis, time.Duration(template.MaxTtl))
605661
if err != nil {
606662
return codersdk.ValidationError{Field: "ttl_ms", Detail: err.Error()}
607663
}
@@ -630,7 +686,11 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
630686
return
631687
}
632688

633-
httpapi.Write(rw, http.StatusOK, nil)
689+
newWorkspace := workspace
690+
newWorkspace.Ttl = dbTTL
691+
aReq.New = newWorkspace
692+
693+
rw.WriteHeader(http.StatusNoContent)
634694
}
635695

636696
func (api *API) putExtendWorkspace(rw http.ResponseWriter, r *http.Request) {

coderd/workspaces_test.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import (
99
"time"
1010

1111
"github.com/google/uuid"
12+
"github.com/stretchr/testify/assert"
1213
"github.com/stretchr/testify/require"
1314

15+
"github.com/coder/coder/coderd/audit"
1416
"github.com/coder/coder/coderd/autobuild/schedule"
1517
"github.com/coder/coder/coderd/coderdtest"
18+
"github.com/coder/coder/coderd/database"
1619
"github.com/coder/coder/coderd/rbac"
1720
"github.com/coder/coder/coderd/util/ptr"
1821
"github.com/coder/coder/codersdk"
@@ -214,12 +217,16 @@ func TestPostWorkspacesByOrganization(t *testing.T) {
214217

215218
t.Run("Create", func(t *testing.T) {
216219
t.Parallel()
217-
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
220+
auditor := audit.NewMock()
221+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
218222
user := coderdtest.CreateFirstUser(t, client)
219223
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
220224
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
221225
coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
222226
_ = coderdtest.CreateWorkspace(t, client, user.OrganizationID, template.ID)
227+
228+
require.Len(t, auditor.AuditLogs, 4)
229+
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[3].Action)
223230
})
224231

225232
t.Run("TemplateNoTTL", func(t *testing.T) {
@@ -949,7 +956,8 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
949956
t.Run(testCase.name, func(t *testing.T) {
950957
t.Parallel()
951958
var (
952-
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
959+
auditor = audit.NewMock()
960+
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
953961
user = coderdtest.CreateFirstUser(t, client)
954962
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
955963
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
@@ -994,6 +1002,9 @@ func TestWorkspaceUpdateAutostart(t *testing.T) {
9941002
require.Equal(t, testCase.expectedNext, next, "unexpected next scheduled autostart time")
9951003
interval := next.Sub(testCase.at)
9961004
require.Equal(t, testCase.expectedInterval, interval, "unexpected interval")
1005+
1006+
require.Len(t, auditor.AuditLogs, 5)
1007+
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[4].Action)
9971008
})
9981009
}
9991010

@@ -1086,7 +1097,8 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
10861097
mutators = append(mutators, testCase.modifyTemplate)
10871098
}
10881099
var (
1089-
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true})
1100+
auditor = audit.NewMock()
1101+
client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
10901102
user = coderdtest.CreateFirstUser(t, client)
10911103
version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
10921104
_ = coderdtest.AwaitTemplateVersionJob(t, client, version.ID)
@@ -1116,6 +1128,9 @@ func TestWorkspaceUpdateTTL(t *testing.T) {
11161128
require.NoError(t, err, "fetch updated workspace")
11171129

11181130
require.Equal(t, testCase.ttlMillis, updated.TTLMillis, "expected autostop ttl to equal requested")
1131+
1132+
require.Len(t, auditor.AuditLogs, 5)
1133+
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[4].Action)
11191134
})
11201135
}
11211136

codersdk/workspaces.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ func (c *Client) UpdateWorkspaceAutostart(ctx context.Context, id uuid.UUID, req
183183
return xerrors.Errorf("update workspace autostart: %w", err)
184184
}
185185
defer res.Body.Close()
186-
if res.StatusCode != http.StatusOK {
186+
if res.StatusCode != http.StatusNoContent {
187187
return readBodyAsError(res)
188188
}
189189
return nil
@@ -203,7 +203,7 @@ func (c *Client) UpdateWorkspaceTTL(ctx context.Context, id uuid.UUID, req Updat
203203
return xerrors.Errorf("update workspace time until shutdown: %w", err)
204204
}
205205
defer res.Body.Close()
206-
if res.StatusCode != http.StatusOK {
206+
if res.StatusCode != http.StatusNoContent {
207207
return readBodyAsError(res)
208208
}
209209
return nil

0 commit comments

Comments
 (0)