4
4
"context"
5
5
"crypto/rand"
6
6
"database/sql"
7
+ "encoding/json"
7
8
"fmt"
8
9
"io"
9
10
"net"
@@ -246,6 +247,9 @@ func Test_ResolveRequest(t *testing.T) {
246
247
// Reset audit logs so cleanup check can pass.
247
248
auditor .ResetLogs ()
248
249
250
+ assertAuditAgent := auditAsserter [database.WorkspaceAgent ](workspace )
251
+ assertAuditApp := auditAsserter [database.WorkspaceApp ](workspace )
252
+
249
253
t .Run ("OK" ,func (t * testing.T ) {
250
254
t .Parallel ()
251
255
@@ -332,18 +336,8 @@ func Test_ResolveRequest(t *testing.T) {
332
336
require .Equal (t ,codersdk .SignedAppTokenCookie ,cookie .Name )
333
337
require .Equal (t ,req .BasePath ,cookie .Path )
334
338
335
- require .True (t ,auditor .Contains (t , database.AuditLog {
336
- OrganizationID :workspace .OrganizationID ,
337
- Action :database .AuditActionOpen ,
338
- ResourceType :audit .ResourceType (appsBySlug [app ]),
339
- ResourceID :audit .ResourceID (appsBySlug [app ]),
340
- ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
341
- UserID :me .ID ,
342
- UserAgent : sql.NullString {Valid :true ,String :auditableUA },
343
- Ip :audit .ParseIP (auditableIP ),
344
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
345
- }),"audit log" )
346
- require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
339
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],me .ID ,nil )
340
+ require .Len (t ,auditor .AuditLogs (),1 ,"audit log count" )
347
341
348
342
var parsedToken workspaceapps.SignedToken
349
343
err := jwtutils .Verify (ctx ,api .AppSigningKeyCache ,cookie .Value ,& parsedToken )
@@ -421,16 +415,7 @@ func Test_ResolveRequest(t *testing.T) {
421
415
require .NotNil (t ,token )
422
416
require .Zero (t ,w .StatusCode )
423
417
424
- require .True (t ,auditor .Contains (t , database.AuditLog {
425
- OrganizationID :workspace .OrganizationID ,
426
- Action :database .AuditActionOpen ,
427
- ResourceType :audit .ResourceType (appsBySlug [app ]),
428
- ResourceID :audit .ResourceID (appsBySlug [app ]),
429
- ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
430
- UserID :secondUser .ID ,
431
- Ip :audit .ParseIP (auditableIP ),
432
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
433
- }),"audit log" )
418
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],secondUser .ID ,nil )
434
419
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
435
420
}
436
421
})
@@ -483,17 +468,8 @@ func Test_ResolveRequest(t *testing.T) {
483
468
t .Fatalf ("expected 200 (or unset) response code, got %d" ,rw .Code )
484
469
}
485
470
486
- require .True (t ,auditor .Contains (t , database.AuditLog {
487
- OrganizationID :workspace .OrganizationID ,
488
- ResourceType :audit .ResourceType (appsBySlug [app ]),
489
- ResourceID :audit .ResourceID (appsBySlug [app ]),
490
- ResourceTarget :audit .ResourceTarget (appsBySlug [app ]),
491
- UserID :uuid .Nil ,// Nil is not verified by Contains, see below.
492
- Ip :audit .ParseIP (auditableIP ),
493
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
494
- }),"audit log" )
471
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],uuid .Nil ,nil )
495
472
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
496
- require .Equal (t ,uuid .Nil ,auditor .AuditLogs ()[0 ].UserID ,"no user ID in audit log" )
497
473
}
498
474
_ = w .Body .Close ()
499
475
}
@@ -617,15 +593,7 @@ func Test_ResolveRequest(t *testing.T) {
617
593
require .Equal (t ,token .AgentNameOrID ,c .agent )
618
594
require .Equal (t ,token .WorkspaceID ,workspace .ID )
619
595
require .Equal (t ,token .AgentID ,agentID )
620
- require .True (t ,auditor .Contains (t , database.AuditLog {
621
- OrganizationID :workspace .OrganizationID ,
622
- ResourceType :audit .ResourceType (appsBySlug [token .AppSlugOrPort ]),
623
- ResourceID :audit .ResourceID (appsBySlug [token .AppSlugOrPort ]),
624
- ResourceTarget :audit .ResourceTarget (appsBySlug [token .AppSlugOrPort ]),
625
- UserID :me .ID ,
626
- Ip :audit .ParseIP (auditableIP ),
627
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
628
- }),"audit log" )
596
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [token .AppSlugOrPort ],me .ID ,nil )
629
597
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
630
598
}else {
631
599
require .Nil (t ,token )
@@ -710,15 +678,7 @@ func Test_ResolveRequest(t *testing.T) {
710
678
require .NoError (t ,err )
711
679
require .Equal (t ,appNameOwner ,parsedToken .AppSlugOrPort )
712
680
713
- require .True (t ,auditor .Contains (t , database.AuditLog {
714
- OrganizationID :workspace .OrganizationID ,
715
- ResourceType :audit .ResourceType (appsBySlug [token .AppSlugOrPort ]),
716
- ResourceID :audit .ResourceID (appsBySlug [token .AppSlugOrPort ]),
717
- ResourceTarget :audit .ResourceTarget (appsBySlug [token .AppSlugOrPort ]),
718
- UserID :me .ID ,
719
- Ip :audit .ParseIP (auditableIP ),
720
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
721
- }),"audit log" )
681
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [appNameOwner ],me .ID ,nil )
722
682
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
723
683
})
724
684
@@ -792,18 +752,9 @@ func Test_ResolveRequest(t *testing.T) {
792
752
require .Equal (t ,req .AppSlugOrPort ,token .AppSlugOrPort )
793
753
require .Equal (t ,"http://127.0.0.1:9090" ,token .AppURL )
794
754
795
- w := rw .Result ()
796
- _ = w .Body .Close ()
797
- require .Equal (t ,http .StatusOK ,w .StatusCode )
798
- require .True (t ,auditor .Contains (t , database.AuditLog {
799
- OrganizationID :workspace .OrganizationID ,
800
- ResourceType :audit .ResourceType (agent ),
801
- ResourceID :audit .ResourceID (agent ),
802
- ResourceTarget :audit .ResourceTarget (agent ),
803
- UserID :me .ID ,
804
- Ip :audit .ParseIP (auditableIP ),
805
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
806
- }),"audit log for agent, not app" )
755
+ assertAuditAgent (t ,rw ,r ,auditor ,agent ,me .ID ,map [string ]any {
756
+ "slug_or_port" :"9090" ,
757
+ })
807
758
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
808
759
})
809
760
@@ -876,17 +827,7 @@ func Test_ResolveRequest(t *testing.T) {
876
827
})
877
828
require .True (t ,ok )
878
829
require .Equal (t ,req .AppSlugOrPort ,token .AppSlugOrPort )
879
- w := rw .Result ()
880
- _ = w .Body .Close ()
881
- require .True (t ,auditor .Contains (t , database.AuditLog {
882
- OrganizationID :workspace .OrganizationID ,
883
- ResourceType :audit .ResourceType (appsBySlug [token .AppSlugOrPort ]),
884
- ResourceID :audit .ResourceID (appsBySlug [token .AppSlugOrPort ]),
885
- ResourceTarget :audit .ResourceTarget (appsBySlug [token .AppSlugOrPort ]),
886
- UserID :me .ID ,
887
- Ip :audit .ParseIP (auditableIP ),
888
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
889
- }),"audit log" )
830
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [appNameEndsInS ],me .ID ,nil )
890
831
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
891
832
})
892
833
@@ -923,17 +864,9 @@ func Test_ResolveRequest(t *testing.T) {
923
864
require .Equal (t ,req .AgentNameOrID ,token .Request .AgentNameOrID )
924
865
require .Empty (t ,token .AppSlugOrPort )
925
866
require .Empty (t ,token .AppURL )
926
- w := rw .Result ()
927
- _ = w .Body .Close ()
928
- require .True (t ,auditor .Contains (t , database.AuditLog {
929
- OrganizationID :workspace .OrganizationID ,
930
- ResourceType :audit .ResourceType (agent ),
931
- ResourceID :audit .ResourceID (agent ),
932
- ResourceTarget :audit .ResourceTarget (agent ),
933
- UserID :me .ID ,
934
- Ip :audit .ParseIP (auditableIP ),
935
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
936
- }),"audit log for agent, not app" )
867
+ assertAuditAgent (t ,rw ,r ,auditor ,agent ,me .ID ,map [string ]any {
868
+ "slug_or_port" :"terminal" ,
869
+ })
937
870
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
938
871
})
939
872
@@ -967,15 +900,7 @@ func Test_ResolveRequest(t *testing.T) {
967
900
})
968
901
require .False (t ,ok )
969
902
require .Nil (t ,token )
970
- w := rw .Result ()
971
- _ = w .Body .Close ()
972
- require .Equal (t ,http .StatusNotFound ,w .StatusCode )
973
- require .True (t ,auditor .Contains (t , database.AuditLog {
974
- OrganizationID :workspace .OrganizationID ,
975
- UserID :secondUser .ID ,
976
- Ip :audit .ParseIP (auditableIP ),
977
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
978
- }),"audit log insufficient permissions" )
903
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [appNameOwner ],secondUser .ID ,nil )
979
904
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
980
905
})
981
906
@@ -1108,12 +1033,7 @@ func Test_ResolveRequest(t *testing.T) {
1108
1033
w := rw .Result ()
1109
1034
defer w .Body .Close ()
1110
1035
require .Equal (t ,http .StatusBadGateway ,w .StatusCode )
1111
- require .True (t ,auditor .Contains (t , database.AuditLog {
1112
- OrganizationID :workspace .OrganizationID ,
1113
- UserID :me .ID ,
1114
- Ip :audit .ParseIP (auditableIP ),
1115
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
1116
- }),"audit log unhealthy agent" )
1036
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [appNameAgentUnhealthy ],me .ID ,nil )
1117
1037
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1118
1038
1119
1039
body ,err := io .ReadAll (w .Body )
@@ -1172,14 +1092,7 @@ func Test_ResolveRequest(t *testing.T) {
1172
1092
})
1173
1093
require .True (t ,ok ,"ResolveRequest failed, should pass even though app is initializing" )
1174
1094
require .NotNil (t ,token )
1175
- w := rw .Result ()
1176
- _ = w .Body .Close ()
1177
- require .True (t ,auditor .Contains (t , database.AuditLog {
1178
- OrganizationID :workspace .OrganizationID ,
1179
- UserID :me .ID ,
1180
- Ip :audit .ParseIP (auditableIP ),
1181
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
1182
- }),"audit log initializing app" )
1095
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [token .AppSlugOrPort ],me .ID ,nil )
1183
1096
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1184
1097
})
1185
1098
@@ -1237,14 +1150,7 @@ func Test_ResolveRequest(t *testing.T) {
1237
1150
})
1238
1151
require .True (t ,ok ,"ResolveRequest failed, should pass even though app is unhealthy" )
1239
1152
require .NotNil (t ,token )
1240
- w := rw .Result ()
1241
- _ = w .Body .Close ()
1242
- require .True (t ,auditor .Contains (t , database.AuditLog {
1243
- OrganizationID :workspace .OrganizationID ,
1244
- UserID :me .ID ,
1245
- Ip :audit .ParseIP (auditableIP ),
1246
- StatusCode :int32 (w .StatusCode ),//nolint:gosec
1247
- }),"audit log unhealthy app" )
1153
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [token .AppSlugOrPort ],me .ID ,nil )
1248
1154
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1249
1155
})
1250
1156
@@ -1281,18 +1187,7 @@ func Test_ResolveRequest(t *testing.T) {
1281
1187
AppRequest :req ,
1282
1188
})
1283
1189
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" )
1190
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],me .ID ,nil )
1296
1191
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log" )
1297
1192
1298
1193
// Second request, no audit log because the session is active.
@@ -1310,8 +1205,6 @@ func Test_ResolveRequest(t *testing.T) {
1310
1205
AppRequest :req ,
1311
1206
})
1312
1207
require .True (t ,ok )
1313
- w = rw .Result ()
1314
- _ = w .Body .Close ()
1315
1208
require .Len (t ,auditor .AuditLogs (),1 ,"single audit log, previous session active" )
1316
1209
1317
1210
// Third request, session timed out, new audit log.
@@ -1330,18 +1223,7 @@ func Test_ResolveRequest(t *testing.T) {
1330
1223
AppRequest :req ,
1331
1224
})
1332
1225
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" )
1226
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],me .ID ,nil )
1345
1227
require .Len (t ,auditor .AuditLogs (),2 ,"two audit logs, session timed out" )
1346
1228
1347
1229
// Fourth request, new IP produces new audit log.
@@ -1360,18 +1242,7 @@ func Test_ResolveRequest(t *testing.T) {
1360
1242
AppRequest :req ,
1361
1243
})
1362
1244
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" )
1245
+ assertAuditApp (t ,rw ,r ,auditor ,appsBySlug [app ],me .ID ,nil )
1375
1246
require .Len (t ,auditor .AuditLogs (),3 ,"three audit logs, new IP" )
1376
1247
}
1377
1248
})
@@ -1413,3 +1284,41 @@ func signedTokenProviderWithAuditor(t testing.TB, provider workspaceapps.SignedT
1413
1284
shallowCopy .WorkspaceAppAuditSessionTimeout = sessionTimeout
1414
1285
return & shallowCopy
1415
1286
}
1287
+
1288
+ func auditAsserter [T audit.Auditable ](workspace codersdk.Workspace )func (t testing.TB ,rr * httptest.ResponseRecorder ,r * http.Request ,auditor * audit.MockAuditor ,auditable T ,userID uuid.UUID ,additionalFields map [string ]any ) {
1289
+ return func (t testing.TB ,rr * httptest.ResponseRecorder ,r * http.Request ,auditor * audit.MockAuditor ,auditable T ,userID uuid.UUID ,additionalFields map [string ]any ) {
1290
+ t .Helper ()
1291
+
1292
+ resp := rr .Result ()
1293
+ defer resp .Body .Close ()
1294
+
1295
+ require .True (t ,auditor .Contains (t , database.AuditLog {
1296
+ OrganizationID :workspace .OrganizationID ,
1297
+ Action :database .AuditActionOpen ,
1298
+ ResourceType :audit .ResourceType (auditable ),
1299
+ ResourceID :audit .ResourceID (auditable ),
1300
+ ResourceTarget :audit .ResourceTarget (auditable ),
1301
+ UserID :userID ,
1302
+ Ip :audit .ParseIP (r .RemoteAddr ),
1303
+ UserAgent : sql.NullString {Valid :r .UserAgent ()!= "" ,String :r .UserAgent ()},
1304
+ StatusCode :int32 (resp .StatusCode ),//nolint:gosec
1305
+ }),"audit log" )
1306
+
1307
+ // Verify additional fields, assume the last log entry.
1308
+ alog := auditor .AuditLogs ()[len (auditor .AuditLogs ())- 1 ]
1309
+
1310
+ // Contains does not verify uuid.Nil.
1311
+ if userID == uuid .Nil {
1312
+ require .Equal (t ,uuid .Nil ,alog .UserID ,"unauthenticated user" )
1313
+ }
1314
+
1315
+ add := make (map [string ]any )
1316
+ if len (alog .AdditionalFields )> 0 {
1317
+ err := json .Unmarshal ([]byte (alog .AdditionalFields ),& add )
1318
+ require .NoError (t ,err ,"audit log unmarhsal additional fields" )
1319
+ }
1320
+ for k ,v := range additionalFields {
1321
+ require .Equal (t ,v ,add [k ],"audit log additional field %s: additional fields: %v" ,k ,add )
1322
+ }
1323
+ }
1324
+ }