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)
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