@@ -372,8 +372,7 @@ func Test_ResolveRequest(t *testing.T) {
372
372
require .WithinDuration (t ,token .Expiry .Time (),secondToken .Expiry .Time (),2 * time .Second )
373
373
secondToken .Expiry = token .Expiry
374
374
require .Equal (t ,token ,secondToken )
375
-
376
- require .Len (t ,auditor .AuditLogs (),1 ,"single audit log, same user and app audit session is active" )
375
+ require .Len (t ,auditor .AuditLogs (),1 ,"no new audit log, FromRequest returned the same token and is not audited" )
377
376
}
378
377
})
379
378
}
@@ -1248,6 +1247,134 @@ func Test_ResolveRequest(t *testing.T) {
1248
1247
}),"audit log unhealthy app" )
1249
1248
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1250
1249
})
1250
+
1251
+ t .Run ("AuditLogging" ,func (t * testing.T ) {
1252
+ t .Parallel ()
1253
+
1254
+ for _ ,app := range allApps {
1255
+ req := (workspaceapps.Request {
1256
+ AccessMethod :workspaceapps .AccessMethodPath ,
1257
+ BasePath :"/app" ,
1258
+ UsernameOrID :me .Username ,
1259
+ WorkspaceNameOrID :workspace .Name ,
1260
+ AgentNameOrID :agentName ,
1261
+ AppSlugOrPort :app ,
1262
+ }).Normalize ()
1263
+
1264
+ auditor := audit .NewMock ()
1265
+ auditableIP := randomIPv6 (t )
1266
+
1267
+ t .Log ("app" ,app )
1268
+
1269
+ // First request, new audit log.
1270
+ rw := httptest .NewRecorder ()
1271
+ r := httptest .NewRequest ("GET" ,"/app" ,nil )
1272
+ r .Header .Set (codersdk .SessionTokenHeader ,client .SessionToken ())
1273
+ r = requestWithAuditorAndRemoteAddr (r ,auditor ,auditableIP )
1274
+
1275
+ _ ,ok := workspaceappsResolveRequest (t ,rw ,r , workspaceapps.ResolveRequestOptions {
1276
+ Logger :api .Logger ,
1277
+ SignedTokenProvider :api .WorkspaceAppsProvider ,
1278
+ DashboardURL :api .AccessURL ,
1279
+ PathAppBaseURL :api .AccessURL ,
1280
+ AppHostname :api .AppHostname ,
1281
+ AppRequest :req ,
1282
+ })
1283
+ require .True (t ,ok )
1284
+ w := rw .Result ()
1285
+ _ = w .Body .Close ()
1286
+ require .True (t ,auditor .Contains (t , database.AuditLog {
1287
+ OrganizationID :workspace .OrganizationID ,
1288
+ Action :database .AuditActionOpen ,
1289
+ ResourceType :audit .ResourceType (appsBySlug [app ]),
1290
+ ResourceID :audit .ResourceID (appsBySlug [app ]),
1291
+ ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
1292
+ UserID :me .ID ,
1293
+ Ip :audit .ParseIP (auditableIP ),
1294
+ StatusCode :int32 (w .StatusCode ),//nolint:gosec
1295
+ }),"audit log 1" )
1296
+ require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1297
+
1298
+ // Second request, no audit log because the session is active.
1299
+ rw = httptest .NewRecorder ()
1300
+ r = httptest .NewRequest ("GET" ,"/app" ,nil )
1301
+ r .Header .Set (codersdk .SessionTokenHeader ,client .SessionToken ())
1302
+ r = requestWithAuditorAndRemoteAddr (r ,auditor ,auditableIP )
1303
+
1304
+ _ ,ok = workspaceappsResolveRequest (t ,rw ,r , workspaceapps.ResolveRequestOptions {
1305
+ Logger :api .Logger ,
1306
+ SignedTokenProvider :api .WorkspaceAppsProvider ,
1307
+ DashboardURL :api .AccessURL ,
1308
+ PathAppBaseURL :api .AccessURL ,
1309
+ AppHostname :api .AppHostname ,
1310
+ AppRequest :req ,
1311
+ })
1312
+ require .True (t ,ok )
1313
+ w = rw .Result ()
1314
+ _ = w .Body .Close ()
1315
+ require .Len (t ,auditor .AuditLogs (),1 ,"single audit log, previous session active" )
1316
+
1317
+ // Third request, session timed out, new audit log.
1318
+ rw = httptest .NewRecorder ()
1319
+ r = httptest .NewRequest ("GET" ,"/app" ,nil )
1320
+ r .Header .Set (codersdk .SessionTokenHeader ,client .SessionToken ())
1321
+ r .RemoteAddr = auditableIP
1322
+
1323
+ sessionTimeoutTokenProvider := signedTokenProviderWithAuditor (t ,api .WorkspaceAppsProvider ,auditor ,0 )
1324
+ _ ,ok = workspaceappsResolveRequest (t ,rw ,r , workspaceapps.ResolveRequestOptions {
1325
+ Logger :api .Logger ,
1326
+ SignedTokenProvider :sessionTimeoutTokenProvider ,
1327
+ DashboardURL :api .AccessURL ,
1328
+ PathAppBaseURL :api .AccessURL ,
1329
+ AppHostname :api .AppHostname ,
1330
+ AppRequest :req ,
1331
+ })
1332
+ require .True (t ,ok )
1333
+ w = rw .Result ()
1334
+ _ = w .Body .Close ()
1335
+ require .True (t ,auditor .Contains (t , database.AuditLog {
1336
+ OrganizationID :workspace .OrganizationID ,
1337
+ Action :database .AuditActionOpen ,
1338
+ ResourceType :audit .ResourceType (appsBySlug [app ]),
1339
+ ResourceID :audit .ResourceID (appsBySlug [app ]),
1340
+ ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
1341
+ UserID :me .ID ,
1342
+ Ip :audit .ParseIP (auditableIP ),
1343
+ StatusCode :int32 (w .StatusCode ),//nolint:gosec
1344
+ }),"audit log 2" )
1345
+ require .Len (t ,auditor .AuditLogs (),2 ,"two audit logs, session timed out" )
1346
+
1347
+ // Fourth request, new IP produces new audit log.
1348
+ auditableIP = randomIPv6 (t )
1349
+ rw = httptest .NewRecorder ()
1350
+ r = httptest .NewRequest ("GET" ,"/app" ,nil )
1351
+ r .Header .Set (codersdk .SessionTokenHeader ,client .SessionToken ())
1352
+ r = requestWithAuditorAndRemoteAddr (r ,auditor ,auditableIP )
1353
+
1354
+ _ ,ok = workspaceappsResolveRequest (t ,rw ,r , workspaceapps.ResolveRequestOptions {
1355
+ Logger :api .Logger ,
1356
+ SignedTokenProvider :api .WorkspaceAppsProvider ,
1357
+ DashboardURL :api .AccessURL ,
1358
+ PathAppBaseURL :api .AccessURL ,
1359
+ AppHostname :api .AppHostname ,
1360
+ AppRequest :req ,
1361
+ })
1362
+ require .True (t ,ok )
1363
+ w = rw .Result ()
1364
+ _ = w .Body .Close ()
1365
+ require .True (t ,auditor .Contains (t , database.AuditLog {
1366
+ OrganizationID :workspace .OrganizationID ,
1367
+ Action :database .AuditActionOpen ,
1368
+ ResourceType :audit .ResourceType (appsBySlug [app ]),
1369
+ ResourceID :audit .ResourceID (appsBySlug [app ]),
1370
+ ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
1371
+ UserID :me .ID ,
1372
+ Ip :audit .ParseIP (auditableIP ),
1373
+ StatusCode :int32 (w .StatusCode ),//nolint:gosec
1374
+ }),"audit log 3" )
1375
+ require .Len (t ,auditor .AuditLogs (),3 ,"three audit logs, new IP" )
1376
+ }
1377
+ })
1251
1378
}
1252
1379
1253
1380
type auditorKey int
@@ -1281,7 +1408,7 @@ func workspaceappsResolveRequest(t testing.TB, w http.ResponseWriter, r *http.Re
1281
1408
if opts .SignedTokenProvider != nil && auditorValue != nil {
1282
1409
auditor ,ok := auditorValue .(audit.Auditor )
1283
1410
require .True (t ,ok ,"auditor is not an audit.Auditor" )
1284
- opts .SignedTokenProvider = signedTokenProviderWithAuditor (t ,opts .SignedTokenProvider ,auditor )
1411
+ opts .SignedTokenProvider = signedTokenProviderWithAuditor (t ,opts .SignedTokenProvider ,auditor , time . Hour )
1285
1412
}
1286
1413
1287
1414
tracing .StatusWriterMiddleware (http .HandlerFunc (func (w http.ResponseWriter ,r * http.Request ) {
@@ -1291,13 +1418,14 @@ func workspaceappsResolveRequest(t testing.TB, w http.ResponseWriter, r *http.Re
1291
1418
return token ,ok
1292
1419
}
1293
1420
1294
- func signedTokenProviderWithAuditor (t testing.TB ,provider workspaceapps.SignedTokenProvider ,auditor audit.Auditor ) workspaceapps.SignedTokenProvider {
1421
+ func signedTokenProviderWithAuditor (t testing.TB ,provider workspaceapps.SignedTokenProvider ,auditor audit.Auditor , sessionTimeout time. Duration ) workspaceapps.SignedTokenProvider {
1295
1422
t .Helper ()
1296
1423
p ,ok := provider .(* workspaceapps.DBTokenProvider )
1297
1424
require .True (t ,ok ,"provider is not a DBTokenProvider" )
1298
1425
1299
1426
shallowCopy := * p
1300
1427
shallowCopy .Auditor = & atomic.Pointer [audit.Auditor ]{}
1301
1428
shallowCopy .Auditor .Store (& auditor )
1429
+ shallowCopy .WorkspaceAppAuditSessionTimeout = sessionTimeout
1302
1430
return & shallowCopy
1303
1431
}