Skip to content

Commit 989d218

Browse files
committed
add specific test for audit
1 parent 4eb2b35 commit 989d218

File tree

2 files changed

+132
-5
lines changed

2 files changed

+132
-5
lines changed

coderd/workspaceapps/db.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,6 @@ func (p *DBTokenProvider) Issue(ctx context.Context, rw http.ResponseWriter, r *
155155
// Verify the user has access to the app.
156156
authed, warnings, err := p.authorizeRequest(r.Context(), authz, dbReq)
157157
if err != nil {
158-
// TODO(mafredri): Audit?
159158
WriteWorkspaceApp500(p.Logger, p.DashboardURL, rw, r, &appReq, err, "verify authz")
160159
return nil, "", false
161160
}

coderd/workspaceapps/db_test.go

Lines changed: 132 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -372,8 +372,7 @@ func Test_ResolveRequest(t *testing.T) {
372372
require.WithinDuration(t, token.Expiry.Time(), secondToken.Expiry.Time(), 2*time.Second)
373373
secondToken.Expiry = token.Expiry
374374
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")
377376
}
378377
})
379378
}
@@ -1248,6 +1247,134 @@ func Test_ResolveRequest(t *testing.T) {
12481247
}), "audit log unhealthy app")
12491248
require.Len(t, auditor.AuditLogs(), 1, "single audit log")
12501249
})
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+
})
12511378
}
12521379

12531380
type auditorKey int
@@ -1281,7 +1408,7 @@ func workspaceappsResolveRequest(t testing.TB, w http.ResponseWriter, r *http.Re
12811408
if opts.SignedTokenProvider != nil && auditorValue != nil {
12821409
auditor, ok := auditorValue.(audit.Auditor)
12831410
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)
12851412
}
12861413

12871414
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
12911418
return token, ok
12921419
}
12931420

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 {
12951422
t.Helper()
12961423
p, ok := provider.(*workspaceapps.DBTokenProvider)
12971424
require.True(t, ok, "provider is not a DBTokenProvider")
12981425

12991426
shallowCopy := *p
13001427
shallowCopy.Auditor = &atomic.Pointer[audit.Auditor]{}
13011428
shallowCopy.Auditor.Store(&auditor)
1429+
shallowCopy.WorkspaceAppAuditSessionTimeout = sessionTimeout
13021430
return &shallowCopy
13031431
}

0 commit comments

Comments
 (0)