Skip to content

Commit a27ac30

Browse files
authored
chore: add sql filter to fetching audit logs (#14070)
* chore: add sql filter to fetching audit logs * use sqlc.embed for audit logs * fix sql query matcher
1 parent d23670a commit a27ac30

File tree

16 files changed

+562
-245
lines changed

16 files changed

+562
-245
lines changed

coderd/audit.go

Lines changed: 43 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -182,17 +182,17 @@ func (api *API) convertAuditLogs(ctx context.Context, dblogs []database.GetAudit
182182
}
183183

184184
func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogsOffsetRow) codersdk.AuditLog {
185-
ip, _ := netip.AddrFromSlice(dblog.Ip.IPNet.IP)
185+
ip, _ := netip.AddrFromSlice(dblog.AuditLog.Ip.IPNet.IP)
186186

187187
diff := codersdk.AuditDiff{}
188-
_ = json.Unmarshal(dblog.Diff, &diff)
188+
_ = json.Unmarshal(dblog.AuditLog.Diff, &diff)
189189

190190
var user *codersdk.User
191191
if dblog.UserUsername.Valid {
192192
// Leaving the organization IDs blank for now; not sure they are useful for
193193
// the audit query anyway?
194194
sdkUser := db2sdk.User(database.User{
195-
ID: dblog.UserID,
195+
ID: dblog.AuditLog.UserID,
196196
Email: dblog.UserEmail.String,
197197
Username: dblog.UserUsername.String,
198198
CreatedAt: dblog.UserCreatedAt.Time,
@@ -211,7 +211,7 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
211211
}
212212

213213
var (
214-
additionalFieldsBytes = []byte(dblog.AdditionalFields)
214+
additionalFieldsBytes = []byte(dblog.AuditLog.AdditionalFields)
215215
additionalFields audit.AdditionalFields
216216
err = json.Unmarshal(additionalFieldsBytes, &additionalFields)
217217
)
@@ -224,7 +224,7 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
224224
WorkspaceOwner: "unknown",
225225
}
226226

227-
dblog.AdditionalFields, err = json.Marshal(resourceInfo)
227+
dblog.AuditLog.AdditionalFields, err = json.Marshal(resourceInfo)
228228
api.Logger.Error(ctx, "marshal additional fields", slog.Error(err))
229229
}
230230

@@ -239,30 +239,30 @@ func (api *API) convertAuditLog(ctx context.Context, dblog database.GetAuditLogs
239239
}
240240

241241
alog := codersdk.AuditLog{
242-
ID: dblog.ID,
243-
RequestID: dblog.RequestID,
244-
Time: dblog.Time,
242+
ID: dblog.AuditLog.ID,
243+
RequestID: dblog.AuditLog.RequestID,
244+
Time: dblog.AuditLog.Time,
245245
// OrganizationID is deprecated.
246-
OrganizationID: dblog.OrganizationID,
246+
OrganizationID: dblog.AuditLog.OrganizationID,
247247
IP: ip,
248-
UserAgent: dblog.UserAgent.String,
249-
ResourceType: codersdk.ResourceType(dblog.ResourceType),
250-
ResourceID: dblog.ResourceID,
251-
ResourceTarget: dblog.ResourceTarget,
252-
ResourceIcon: dblog.ResourceIcon,
253-
Action: codersdk.AuditAction(dblog.Action),
248+
UserAgent: dblog.AuditLog.UserAgent.String,
249+
ResourceType: codersdk.ResourceType(dblog.AuditLog.ResourceType),
250+
ResourceID: dblog.AuditLog.ResourceID,
251+
ResourceTarget: dblog.AuditLog.ResourceTarget,
252+
ResourceIcon: dblog.AuditLog.ResourceIcon,
253+
Action: codersdk.AuditAction(dblog.AuditLog.Action),
254254
Diff: diff,
255-
StatusCode: dblog.StatusCode,
256-
AdditionalFields: dblog.AdditionalFields,
255+
StatusCode: dblog.AuditLog.StatusCode,
256+
AdditionalFields: dblog.AuditLog.AdditionalFields,
257257
User: user,
258258
Description: auditLogDescription(dblog),
259259
ResourceLink: resourceLink,
260260
IsDeleted: isDeleted,
261261
}
262262

263-
if dblog.OrganizationID != uuid.Nil {
263+
if dblog.AuditLog.OrganizationID != uuid.Nil {
264264
alog.Organization = &codersdk.MinimalOrganization{
265-
ID: dblog.OrganizationID,
265+
ID: dblog.AuditLog.OrganizationID,
266266
Name: dblog.OrganizationName,
267267
DisplayName: dblog.OrganizationDisplayName,
268268
Icon: dblog.OrganizationIcon,
@@ -276,32 +276,32 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
276276
b := strings.Builder{}
277277
// NOTE: WriteString always returns a nil error, so we never check it
278278
_, _ = b.WriteString("{user} ")
279-
if alog.StatusCode >= 400 {
279+
if alog.AuditLog.StatusCode >= 400 {
280280
_, _ = b.WriteString("unsuccessfully attempted to ")
281-
_, _ = b.WriteString(string(alog.Action))
281+
_, _ = b.WriteString(string(alog.AuditLog.Action))
282282
} else {
283-
_, _ = b.WriteString(codersdk.AuditAction(alog.Action).Friendly())
283+
_, _ = b.WriteString(codersdk.AuditAction(alog.AuditLog.Action).Friendly())
284284
}
285285

286286
// API Key resources (used for authentication) do not have targets and follow the below format:
287287
// "User {logged in | logged out | registered}"
288-
if alog.ResourceType == database.ResourceTypeApiKey &&
289-
(alog.Action == database.AuditActionLogin || alog.Action == database.AuditActionLogout || alog.Action == database.AuditActionRegister) {
288+
if alog.AuditLog.ResourceType == database.ResourceTypeApiKey &&
289+
(alog.AuditLog.Action == database.AuditActionLogin || alog.AuditLog.Action == database.AuditActionLogout || alog.AuditLog.Action == database.AuditActionRegister) {
290290
return b.String()
291291
}
292292

293293
// We don't display the name (target) for git ssh keys. It's fairly long and doesn't
294294
// make too much sense to display.
295-
if alog.ResourceType == database.ResourceTypeGitSshKey {
295+
if alog.AuditLog.ResourceType == database.ResourceTypeGitSshKey {
296296
_, _ = b.WriteString(" the ")
297-
_, _ = b.WriteString(codersdk.ResourceType(alog.ResourceType).FriendlyString())
297+
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())
298298
return b.String()
299299
}
300300

301301
_, _ = b.WriteString(" ")
302-
_, _ = b.WriteString(codersdk.ResourceType(alog.ResourceType).FriendlyString())
302+
_, _ = b.WriteString(codersdk.ResourceType(alog.AuditLog.ResourceType).FriendlyString())
303303

304-
if alog.ResourceType == database.ResourceTypeConvertLogin {
304+
if alog.AuditLog.ResourceType == database.ResourceTypeConvertLogin {
305305
_, _ = b.WriteString(" to")
306306
}
307307

@@ -311,9 +311,9 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
311311
}
312312

313313
func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.GetAuditLogsOffsetRow) bool {
314-
switch alog.ResourceType {
314+
switch alog.AuditLog.ResourceType {
315315
case database.ResourceTypeTemplate:
316-
template, err := api.Database.GetTemplateByID(ctx, alog.ResourceID)
316+
template, err := api.Database.GetTemplateByID(ctx, alog.AuditLog.ResourceID)
317317
if err != nil {
318318
if xerrors.Is(err, sql.ErrNoRows) {
319319
return true
@@ -322,7 +322,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
322322
}
323323
return template.Deleted
324324
case database.ResourceTypeUser:
325-
user, err := api.Database.GetUserByID(ctx, alog.ResourceID)
325+
user, err := api.Database.GetUserByID(ctx, alog.AuditLog.ResourceID)
326326
if err != nil {
327327
if xerrors.Is(err, sql.ErrNoRows) {
328328
return true
@@ -331,7 +331,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
331331
}
332332
return user.Deleted
333333
case database.ResourceTypeWorkspace:
334-
workspace, err := api.Database.GetWorkspaceByID(ctx, alog.ResourceID)
334+
workspace, err := api.Database.GetWorkspaceByID(ctx, alog.AuditLog.ResourceID)
335335
if err != nil {
336336
if xerrors.Is(err, sql.ErrNoRows) {
337337
return true
@@ -340,7 +340,7 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
340340
}
341341
return workspace.Deleted
342342
case database.ResourceTypeWorkspaceBuild:
343-
workspaceBuild, err := api.Database.GetWorkspaceBuildByID(ctx, alog.ResourceID)
343+
workspaceBuild, err := api.Database.GetWorkspaceBuildByID(ctx, alog.AuditLog.ResourceID)
344344
if err != nil {
345345
if xerrors.Is(err, sql.ErrNoRows) {
346346
return true
@@ -357,15 +357,15 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
357357
}
358358
return workspace.Deleted
359359
case database.ResourceTypeOauth2ProviderApp:
360-
_, err := api.Database.GetOAuth2ProviderAppByID(ctx, alog.ResourceID)
360+
_, err := api.Database.GetOAuth2ProviderAppByID(ctx, alog.AuditLog.ResourceID)
361361
if xerrors.Is(err, sql.ErrNoRows) {
362362
return true
363363
} else if err != nil {
364364
api.Logger.Error(ctx, "unable to fetch oauth2 app", slog.Error(err))
365365
}
366366
return false
367367
case database.ResourceTypeOauth2ProviderAppSecret:
368-
_, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.ResourceID)
368+
_, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.AuditLog.ResourceID)
369369
if xerrors.Is(err, sql.ErrNoRows) {
370370
return true
371371
} else if err != nil {
@@ -378,17 +378,17 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
378378
}
379379

380380
func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAuditLogsOffsetRow, additionalFields audit.AdditionalFields) string {
381-
switch alog.ResourceType {
381+
switch alog.AuditLog.ResourceType {
382382
case database.ResourceTypeTemplate:
383383
return fmt.Sprintf("/templates/%s",
384-
alog.ResourceTarget)
384+
alog.AuditLog.ResourceTarget)
385385

386386
case database.ResourceTypeUser:
387387
return fmt.Sprintf("/users?filter=%s",
388-
alog.ResourceTarget)
388+
alog.AuditLog.ResourceTarget)
389389

390390
case database.ResourceTypeWorkspace:
391-
workspace, getWorkspaceErr := api.Database.GetWorkspaceByID(ctx, alog.ResourceID)
391+
workspace, getWorkspaceErr := api.Database.GetWorkspaceByID(ctx, alog.AuditLog.ResourceID)
392392
if getWorkspaceErr != nil {
393393
return ""
394394
}
@@ -397,13 +397,13 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
397397
return ""
398398
}
399399
return fmt.Sprintf("/@%s/%s",
400-
workspaceOwner.Username, alog.ResourceTarget)
400+
workspaceOwner.Username, alog.AuditLog.ResourceTarget)
401401

402402
case database.ResourceTypeWorkspaceBuild:
403403
if len(additionalFields.WorkspaceName) == 0 || len(additionalFields.BuildNumber) == 0 {
404404
return ""
405405
}
406-
workspaceBuild, getWorkspaceBuildErr := api.Database.GetWorkspaceBuildByID(ctx, alog.ResourceID)
406+
workspaceBuild, getWorkspaceBuildErr := api.Database.GetWorkspaceBuildByID(ctx, alog.AuditLog.ResourceID)
407407
if getWorkspaceBuildErr != nil {
408408
return ""
409409
}
@@ -419,10 +419,10 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
419419
workspaceOwner.Username, additionalFields.WorkspaceName, additionalFields.BuildNumber)
420420

421421
case database.ResourceTypeOauth2ProviderApp:
422-
return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", alog.ResourceID)
422+
return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", alog.AuditLog.ResourceID)
423423

424424
case database.ResourceTypeOauth2ProviderAppSecret:
425-
secret, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.ResourceID)
425+
secret, err := api.Database.GetOAuth2ProviderAppSecretByID(ctx, alog.AuditLog.ResourceID)
426426
if err != nil {
427427
return ""
428428
}

coderd/audit_internal_test.go

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,45 +18,55 @@ func TestAuditLogDescription(t *testing.T) {
1818
{
1919
name: "mainline",
2020
alog: database.GetAuditLogsOffsetRow{
21-
Action: database.AuditActionCreate,
22-
StatusCode: 200,
23-
ResourceType: database.ResourceTypeWorkspace,
21+
AuditLog: database.AuditLog{
22+
Action: database.AuditActionCreate,
23+
StatusCode: 200,
24+
ResourceType: database.ResourceTypeWorkspace,
25+
},
2426
},
2527
want: "{user} created workspace {target}",
2628
},
2729
{
2830
name: "unsuccessful",
2931
alog: database.GetAuditLogsOffsetRow{
30-
Action: database.AuditActionCreate,
31-
StatusCode: 400,
32-
ResourceType: database.ResourceTypeWorkspace,
32+
AuditLog: database.AuditLog{
33+
Action: database.AuditActionCreate,
34+
StatusCode: 400,
35+
ResourceType: database.ResourceTypeWorkspace,
36+
},
3337
},
3438
want: "{user} unsuccessfully attempted to create workspace {target}",
3539
},
3640
{
3741
name: "login",
3842
alog: database.GetAuditLogsOffsetRow{
39-
Action: database.AuditActionLogin,
40-
StatusCode: 200,
41-
ResourceType: database.ResourceTypeApiKey,
43+
AuditLog: database.AuditLog{
44+
Action: database.AuditActionLogin,
45+
StatusCode: 200,
46+
ResourceType: database.ResourceTypeApiKey,
47+
},
4248
},
4349
want: "{user} logged in",
4450
},
4551
{
4652
name: "unsuccessful_login",
4753
alog: database.GetAuditLogsOffsetRow{
48-
Action: database.AuditActionLogin,
49-
StatusCode: 401,
50-
ResourceType: database.ResourceTypeApiKey,
54+
AuditLog: database.AuditLog{
55+
Action: database.AuditActionLogin,
56+
StatusCode: 401,
57+
ResourceType: database.ResourceTypeApiKey,
58+
},
5159
},
5260
want: "{user} unsuccessfully attempted to login",
5361
},
5462
{
5563
name: "gitsshkey",
5664
alog: database.GetAuditLogsOffsetRow{
57-
Action: database.AuditActionDelete,
58-
StatusCode: 200,
59-
ResourceType: database.ResourceTypeGitSshKey,
65+
AuditLog: database.AuditLog{
66+
Action: database.AuditActionDelete,
67+
StatusCode: 200,
68+
ResourceType: database.ResourceTypeGitSshKey,
69+
},
6070
},
6171
want: "{user} deleted the git ssh key",
6272
},

coderd/audit_test.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"net/http"
87
"strconv"
98
"testing"
109
"time"
@@ -163,19 +162,18 @@ func TestAuditLogs(t *testing.T) {
163162
})
164163
require.NoError(t, err)
165164

166-
// Fetching audit logs without an organization selector should fail
167-
_, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
165+
// Fetching audit logs without an organization selector should only
166+
// return organization audit logs the org admin is an admin of.
167+
alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
168168
Pagination: codersdk.Pagination{
169169
Limit: 5,
170170
},
171171
})
172-
var sdkError *codersdk.Error
173-
require.Error(t, err)
174-
require.ErrorAsf(t, err, &sdkError, "error should be of type *codersdk.Error")
175-
require.Equal(t, http.StatusForbidden, sdkError.StatusCode())
172+
require.NoError(t, err)
173+
require.Len(t, alogs.AuditLogs, 1)
176174

177175
// Using the organization selector allows the org admin to fetch audit logs
178-
alogs, err := orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
176+
alogs, err = orgAdmin.AuditLogs(ctx, codersdk.AuditLogsRequest{
179177
SearchQuery: fmt.Sprintf("organization:%s", owner.OrganizationID.String()),
180178
Pagination: codersdk.Pagination{
181179
Limit: 5,

coderd/database/dbauthz/dbauthz.go

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,22 +1248,12 @@ func (q *querier) GetApplicationName(ctx context.Context) (string, error) {
12481248
}
12491249

12501250
func (q *querier) GetAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams) ([]database.GetAuditLogsOffsetRow, error) {
1251-
// To optimize the authz checks for audit logs, do not run an authorize
1252-
// check on each individual audit log row. In practice, audit logs are either
1253-
// fetched from a global or an organization scope.
1254-
// Applying a SQL filter would slow down the query for no benefit on how this query is
1255-
// actually used.
1256-
1257-
object := rbac.ResourceAuditLog
1258-
if arg.OrganizationID != uuid.Nil {
1259-
object = object.InOrg(arg.OrganizationID)
1260-
}
1261-
1262-
if err := q.authorizeContext(ctx, policy.ActionRead, object); err != nil {
1263-
return nil, err
1251+
prep, err := prepareSQLFilter(ctx, q.auth, policy.ActionRead, rbac.ResourceAuditLog.Type)
1252+
if err != nil {
1253+
return nil, xerrors.Errorf("(dev error) prepare sql filter: %w", err)
12641254
}
12651255

1266-
return q.db.GetAuditLogsOffset(ctx, arg)
1256+
return q.db.GetAuthorizedAuditLogsOffset(ctx, arg, prep)
12671257
}
12681258

12691259
func (q *querier) GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (database.GetAuthorizationUserRolesRow, error) {
@@ -3852,3 +3842,7 @@ func (q *querier) GetAuthorizedUsers(ctx context.Context, arg database.GetUsersP
38523842
// GetUsers is authenticated.
38533843
return q.GetUsers(ctx, arg)
38543844
}
3845+
3846+
func (q *querier) GetAuthorizedAuditLogsOffset(ctx context.Context, arg database.GetAuditLogsOffsetParams, _ rbac.PreparedAuthorized) ([]database.GetAuditLogsOffsetRow, error) {
3847+
return q.GetAuditLogsOffset(ctx, arg)
3848+
}

coderd/database/dbauthz/dbauthz_test.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,14 @@ func (s *MethodTestSuite) TestAuditLogs() {
266266
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
267267
check.Args(database.GetAuditLogsOffsetParams{
268268
LimitOpt: 10,
269-
}).Asserts(rbac.ResourceAuditLog, policy.ActionRead)
269+
}).Asserts()
270+
}))
271+
s.Run("GetAuthorizedAuditLogsOffset", s.Subtest(func(db database.Store, check *expects) {
272+
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
273+
_ = dbgen.AuditLog(s.T(), db, database.AuditLog{})
274+
check.Args(database.GetAuditLogsOffsetParams{
275+
LimitOpt: 10,
276+
}, emptyPreparedAuthorized{}).Asserts()
270277
}))
271278
}
272279

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy