Skip to content

Commit abb804f

Browse files
authored
feat: add template/template version auditing (#3965)
1 parent d380c94 commit abb804f

File tree

8 files changed

+158
-45
lines changed

8 files changed

+158
-45
lines changed

coderd/database/databasefake/databasefake.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,7 @@ func (q *fakeQuerier) GetTemplateByOrganizationAndName(_ context.Context, arg da
920920
return database.Template{}, sql.ErrNoRows
921921
}
922922

923-
func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.UpdateTemplateMetaByIDParams) error {
923+
func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.UpdateTemplateMetaByIDParams) (database.Template, error) {
924924
q.mutex.RLock()
925925
defer q.mutex.RUnlock()
926926

@@ -935,10 +935,10 @@ func (q *fakeQuerier) UpdateTemplateMetaByID(_ context.Context, arg database.Upd
935935
tpl.MaxTtl = arg.MaxTtl
936936
tpl.MinAutostartInterval = arg.MinAutostartInterval
937937
q.templates[idx] = tpl
938-
return nil
938+
return tpl, nil
939939
}
940940

941-
return sql.ErrNoRows
941+
return database.Template{}, sql.ErrNoRows
942942
}
943943

944944
func (q *fakeQuerier) GetTemplatesWithFilter(_ context.Context, arg database.GetTemplatesWithFilterParams) ([]database.Template, error) {

coderd/database/querier.go

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+20-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/templates.sql

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ SET
9191
WHERE
9292
id = $1;
9393

94-
-- name: UpdateTemplateMetaByID :exec
94+
-- name: UpdateTemplateMetaByID :one
9595
UPDATE
9696
templates
9797
SET

coderd/templates.go

+62-19
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/moby/moby/pkg/namesgenerator"
1717
"golang.org/x/xerrors"
1818

19+
"github.com/coder/coder/coderd/audit"
1920
"github.com/coder/coder/coderd/database"
2021
"github.com/coder/coder/coderd/httpapi"
2122
"github.com/coder/coder/coderd/httpmw"
@@ -82,7 +83,18 @@ func (api *API) template(rw http.ResponseWriter, r *http.Request) {
8283
}
8384

8485
func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
85-
template := httpmw.TemplateParam(r)
86+
var (
87+
template = httpmw.TemplateParam(r)
88+
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
89+
Features: api.FeaturesService,
90+
Log: api.Logger,
91+
Request: r,
92+
Action: database.AuditActionDelete,
93+
})
94+
)
95+
defer commitAudit()
96+
aReq.Old = template
97+
8698
if !api.Authorize(r, rbac.ActionDelete, template) {
8799
httpapi.ResourceNotFound(rw)
88100
return
@@ -91,10 +103,7 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
91103
workspaces, err := api.Database.GetWorkspaces(r.Context(), database.GetWorkspacesParams{
92104
TemplateIds: []uuid.UUID{template.ID},
93105
})
94-
if errors.Is(err, sql.ErrNoRows) {
95-
err = nil
96-
}
97-
if err != nil {
106+
if err != nil && !errors.Is(err, sql.ErrNoRows) {
98107
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
99108
Message: "Internal error fetching workspaces by template id.",
100109
Detail: err.Error(),
@@ -126,9 +135,26 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
126135

127136
// Create a new template in an organization.
128137
func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Request) {
129-
var createTemplate codersdk.CreateTemplateRequest
130-
organization := httpmw.OrganizationParam(r)
131-
apiKey := httpmw.APIKey(r)
138+
var (
139+
createTemplate codersdk.CreateTemplateRequest
140+
organization = httpmw.OrganizationParam(r)
141+
apiKey = httpmw.APIKey(r)
142+
templateAudit, commitTemplateAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
143+
Features: api.FeaturesService,
144+
Log: api.Logger,
145+
Request: r,
146+
Action: database.AuditActionCreate,
147+
})
148+
templateVersionAudit, commitTemplateVersionAudit = audit.InitRequest[database.TemplateVersion](rw, &audit.RequestParams{
149+
Features: api.FeaturesService,
150+
Log: api.Logger,
151+
Request: r,
152+
Action: database.AuditActionWrite,
153+
})
154+
)
155+
defer commitTemplateAudit()
156+
defer commitTemplateVersionAudit()
157+
132158
if !api.Authorize(r, rbac.ActionCreate, rbac.ResourceTemplate.InOrg(organization.ID)) {
133159
httpapi.ResourceNotFound(rw)
134160
return
@@ -175,6 +201,8 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
175201
})
176202
return
177203
}
204+
templateVersionAudit.Old = templateVersion
205+
178206
importJob, err := api.Database.GetProvisionerJobByID(r.Context(), templateVersion.JobID)
179207
if err != nil {
180208
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
@@ -234,6 +262,8 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
234262
return xerrors.Errorf("insert template: %s", err)
235263
}
236264

265+
templateAudit.New = dbTemplate
266+
237267
err = db.UpdateTemplateVersionByID(r.Context(), database.UpdateTemplateVersionByIDParams{
238268
ID: templateVersion.ID,
239269
TemplateID: uuid.NullUUID{
@@ -245,6 +275,12 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
245275
if err != nil {
246276
return xerrors.Errorf("insert template version: %s", err)
247277
}
278+
newTemplateVersion := templateVersion
279+
newTemplateVersion.TemplateID = uuid.NullUUID{
280+
UUID: dbTemplate.ID,
281+
Valid: true,
282+
}
283+
templateVersionAudit.New = newTemplateVersion
248284

249285
for _, parameterValue := range createTemplate.ParameterValues {
250286
_, err = db.InsertParameterValue(r.Context(), database.InsertParameterValueParams{
@@ -397,7 +433,18 @@ func (api *API) templateByOrganizationAndName(rw http.ResponseWriter, r *http.Re
397433
}
398434

399435
func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
400-
template := httpmw.TemplateParam(r)
436+
var (
437+
template = httpmw.TemplateParam(r)
438+
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
439+
Features: api.FeaturesService,
440+
Log: api.Logger,
441+
Request: r,
442+
Action: database.AuditActionWrite,
443+
})
444+
)
445+
defer commitAudit()
446+
aReq.Old = template
447+
401448
if !api.Authorize(r, rbac.ActionUpdate, template) {
402449
httpapi.ResourceNotFound(rw)
403450
return
@@ -474,36 +521,32 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
474521
minAutostartInterval = time.Duration(template.MinAutostartInterval)
475522
}
476523

477-
if err := s.UpdateTemplateMetaByID(r.Context(), database.UpdateTemplateMetaByIDParams{
524+
updated, err = s.UpdateTemplateMetaByID(r.Context(), database.UpdateTemplateMetaByIDParams{
478525
ID: template.ID,
479526
UpdatedAt: database.Now(),
480527
Name: name,
481528
Description: desc,
482529
Icon: icon,
483530
MaxTtl: int64(maxTTL),
484531
MinAutostartInterval: int64(minAutostartInterval),
485-
}); err != nil {
486-
return err
487-
}
488-
489-
updated, err = s.GetTemplateByID(r.Context(), template.ID)
532+
})
490533
if err != nil {
491534
return err
492535
}
536+
493537
return nil
494538
})
495539
if err != nil {
496-
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
497-
Message: "Internal error updating template metadata.",
498-
Detail: err.Error(),
499-
})
540+
httpapi.InternalServerError(rw, err)
500541
return
501542
}
502543

503544
if updated.UpdatedAt.IsZero() {
545+
aReq.New = template
504546
httpapi.Write(rw, http.StatusNotModified, nil)
505547
return
506548
}
549+
aReq.New = updated
507550

508551
createdByNameMap, err := getCreatedByNamesByTemplateIDs(r.Context(), api.Database, []database.Template{updated})
509552
if err != nil {

coderd/templates_test.go

+19-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@ import (
1313
"cdr.dev/slog/sloggers/slogtest"
1414

1515
"github.com/coder/coder/agent"
16+
"github.com/coder/coder/coderd/audit"
1617
"github.com/coder/coder/coderd/coderdtest"
18+
"github.com/coder/coder/coderd/database"
1719
"github.com/coder/coder/coderd/rbac"
1820
"github.com/coder/coder/coderd/util/ptr"
1921
"github.com/coder/coder/codersdk"
@@ -78,7 +80,8 @@ func TestPostTemplateByOrganization(t *testing.T) {
7880
t.Parallel()
7981
t.Run("Create", func(t *testing.T) {
8082
t.Parallel()
81-
client := coderdtest.New(t, nil)
83+
auditor := audit.NewMock()
84+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
8285
user := coderdtest.CreateFirstUser(t, client)
8386
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
8487

@@ -92,6 +95,11 @@ func TestPostTemplateByOrganization(t *testing.T) {
9295

9396
assert.Equal(t, expected.Name, got.Name)
9497
assert.Equal(t, expected.Description, got.Description)
98+
99+
require.Len(t, auditor.AuditLogs, 3)
100+
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[0].Action)
101+
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[1].Action)
102+
assert.Equal(t, database.AuditActionCreate, auditor.AuditLogs[2].Action)
95103
})
96104

97105
t.Run("AlreadyExists", func(t *testing.T) {
@@ -291,7 +299,8 @@ func TestPatchTemplateMeta(t *testing.T) {
291299
t.Run("Modified", func(t *testing.T) {
292300
t.Parallel()
293301

294-
client := coderdtest.New(t, nil)
302+
auditor := audit.NewMock()
303+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
295304
user := coderdtest.CreateFirstUser(t, client)
296305
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
297306
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID, func(ctr *codersdk.CreateTemplateRequest) {
@@ -332,6 +341,9 @@ func TestPatchTemplateMeta(t *testing.T) {
332341
assert.Equal(t, req.Icon, updated.Icon)
333342
assert.Equal(t, req.MaxTTLMillis, updated.MaxTTLMillis)
334343
assert.Equal(t, req.MinAutostartIntervalMillis, updated.MinAutostartIntervalMillis)
344+
345+
require.Len(t, auditor.AuditLogs, 4)
346+
assert.Equal(t, database.AuditActionWrite, auditor.AuditLogs[3].Action)
335347
})
336348

337349
t.Run("NoMaxTTL", func(t *testing.T) {
@@ -514,7 +526,8 @@ func TestDeleteTemplate(t *testing.T) {
514526

515527
t.Run("NoWorkspaces", func(t *testing.T) {
516528
t.Parallel()
517-
client := coderdtest.New(t, nil)
529+
auditor := audit.NewMock()
530+
client := coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true, Auditor: auditor})
518531
user := coderdtest.CreateFirstUser(t, client)
519532
version := coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil)
520533
template := coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID)
@@ -524,6 +537,9 @@ func TestDeleteTemplate(t *testing.T) {
524537

525538
err := client.DeleteTemplate(ctx, template.ID)
526539
require.NoError(t, err)
540+
541+
require.Len(t, auditor.AuditLogs, 4)
542+
assert.Equal(t, database.AuditActionDelete, auditor.AuditLogs[3].Action)
527543
})
528544

529545
t.Run("Workspaces", func(t *testing.T) {

0 commit comments

Comments
 (0)