Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit9d5f9ad

Browse files
mafredriEdwardAngert
authored andcommitted
feat: add support for workspace app audit (#16801)
This change adds support for workspace app auditing.To avoid audit log spam, we introduce the concept of app audit sessions.An audit session is unique per workspace app, user, ip, user agent andhttp status code. The sessions are stored in a separate table from auditlogs to allow use-case specific optimizations. Sessions are ephemeraland the table does not function as a log.The logic for auditing is placed in the DBTokenProvider for workspaceapps so that wsproxies are included.This is the final change affecting the API fo#15139.Updates#15139
1 parentd6d723f commit9d5f9ad

25 files changed

+1042
-159
lines changed

‎coderd/audit.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,14 @@ func auditLogDescription(alog database.GetAuditLogsOffsetRow) string {
282282
_,_=b.WriteString("{user} ")
283283
}
284284

285-
ifalog.AuditLog.StatusCode>=400 {
285+
switch {
286+
casealog.AuditLog.StatusCode==int32(http.StatusSeeOther):
287+
_,_=b.WriteString("was redirected attempting to ")
288+
_,_=b.WriteString(string(alog.AuditLog.Action))
289+
casealog.AuditLog.StatusCode>=400:
286290
_,_=b.WriteString("unsuccessfully attempted to ")
287291
_,_=b.WriteString(string(alog.AuditLog.Action))
288-
}else {
292+
default:
289293
_,_=b.WriteString(codersdk.AuditAction(alog.AuditLog.Action).Friendly())
290294
}
291295

‎coderd/audit/audit.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (a *MockAuditor) Contains(t testing.TB, expected database.AuditLog) bool {
9393
t.Logf("audit log %d: expected UserID %s, got %s",idx+1,expected.UserID,al.UserID)
9494
continue
9595
}
96-
ifexpected.OrganizationID!=uuid.Nil&&al.UserID!=expected.UserID {
96+
ifexpected.OrganizationID!=uuid.Nil&&al.OrganizationID!=expected.OrganizationID {
9797
t.Logf("audit log %d: expected OrganizationID %s, got %s",idx+1,expected.OrganizationID,al.OrganizationID)
9898
continue
9999
}

‎coderd/audit/request.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ type BackgroundAuditParams[T Auditable] struct {
7171
Action database.AuditAction
7272
OrganizationID uuid.UUID
7373
IPstring
74+
UserAgentstring
7475
// todo: this should automatically marshal an interface{} instead of accepting a raw message.
7576
AdditionalFields json.RawMessage
7677

@@ -422,7 +423,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
422423
action=req.Action
423424
}
424425

425-
ip:=parseIP(p.Request.RemoteAddr)
426+
ip:=ParseIP(p.Request.RemoteAddr)
426427
auditLog:= database.AuditLog{
427428
ID:uuid.New(),
428429
Time:dbtime.Now(),
@@ -453,7 +454,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
453454
// BackgroundAudit creates an audit log for a background event.
454455
// The audit log is committed upon invocation.
455456
funcBackgroundAudit[TAuditable](ctx context.Context,p*BackgroundAuditParams[T]) {
456-
ip:=parseIP(p.IP)
457+
ip:=ParseIP(p.IP)
457458

458459
diff:=Diff(p.Audit,p.Old,p.New)
459460
varerrerror
@@ -479,7 +480,7 @@ func BackgroundAudit[T Auditable](ctx context.Context, p *BackgroundAuditParams[
479480
UserID:p.UserID,
480481
OrganizationID:requireOrgID[T](ctx,p.OrganizationID,p.Log),
481482
Ip:ip,
482-
UserAgent: sql.NullString{},
483+
UserAgent: sql.NullString{Valid:p.UserAgent!="",String:p.UserAgent},
483484
ResourceType:either(p.Old,p.New,ResourceType[T],p.Action),
484485
ResourceID:either(p.Old,p.New,ResourceID[T],p.Action),
485486
ResourceTarget:either(p.Old,p.New,ResourceTarget[T],p.Action),
@@ -566,7 +567,7 @@ func either[T Auditable, R any](old, new T, fn func(T) R, auditAction database.A
566567
panic("both old and new are nil")
567568
}
568569

569-
funcparseIP(ipStrstring) pqtype.Inet {
570+
funcParseIP(ipStrstring) pqtype.Inet {
570571
ip:=net.ParseIP(ipStr)
571572
ipNet:= net.IPNet{}
572573
ifip!=nil {

‎coderd/coderd.go

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,10 @@ type Options struct {
226226
UpdateAgentMetricsfunc(ctx context.Context,labels prometheusmetrics.AgentMetricLabels,metrics []*agentproto.Stats_Metric)
227227
StatsBatcher workspacestats.Batcher
228228

229+
// WorkspaceAppAuditSessionTimeout allows changing the timeout for audit
230+
// sessions. Raising or lowering this value will directly affect the write
231+
// load of the audit log table. This is used for testing. Default 1 hour.
232+
WorkspaceAppAuditSessionTimeout time.Duration
229233
WorkspaceAppsStatsCollectorOptions workspaceapps.StatsCollectorOptions
230234

231235
// This janky function is used in telemetry to parse fields out of the raw
@@ -534,16 +538,6 @@ func New(options *Options) *API {
534538
Authorizer:options.Authorizer,
535539
Logger:options.Logger,
536540
},
537-
WorkspaceAppsProvider:workspaceapps.NewDBTokenProvider(
538-
options.Logger.Named("workspaceapps"),
539-
options.AccessURL,
540-
options.Authorizer,
541-
options.Database,
542-
options.DeploymentValues,
543-
oauthConfigs,
544-
options.AgentInactiveDisconnectTimeout,
545-
options.AppSigningKeyCache,
546-
),
547541
metricsCache:metricsCache,
548542
Auditor: atomic.Pointer[audit.Auditor]{},
549543
TailnetCoordinator: atomic.Pointer[tailnet.Coordinator]{},
@@ -561,6 +555,18 @@ func New(options *Options) *API {
561555
),
562556
dbRolluper:options.DatabaseRolluper,
563557
}
558+
api.WorkspaceAppsProvider=workspaceapps.NewDBTokenProvider(
559+
options.Logger.Named("workspaceapps"),
560+
options.AccessURL,
561+
options.Authorizer,
562+
&api.Auditor,
563+
options.Database,
564+
options.DeploymentValues,
565+
oauthConfigs,
566+
options.AgentInactiveDisconnectTimeout,
567+
options.WorkspaceAppAuditSessionTimeout,
568+
options.AppSigningKeyCache,
569+
)
564570

565571
f:=appearance.NewDefaultFetcher(api.DeploymentValues.DocsURL.String())
566572
api.AppearanceFetcher.Store(&f)

‎coderd/database/dbauthz/dbauthz.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4615,6 +4615,13 @@ func (q *querier) UpsertWorkspaceAgentPortShare(ctx context.Context, arg databas
46154615
returnq.db.UpsertWorkspaceAgentPortShare(ctx,arg)
46164616
}
46174617

4618+
func (q*querier)UpsertWorkspaceAppAuditSession(ctx context.Context,arg database.UpsertWorkspaceAppAuditSessionParams) (time.Time,error) {
4619+
iferr:=q.authorizeContext(ctx,policy.ActionUpdate,rbac.ResourceSystem);err!=nil {
4620+
return time.Time{},err
4621+
}
4622+
returnq.db.UpsertWorkspaceAppAuditSession(ctx,arg)
4623+
}
4624+
46184625
func (q*querier)GetAuthorizedTemplates(ctx context.Context,arg database.GetTemplatesWithFilterParams,_ rbac.PreparedAuthorized) ([]database.Template,error) {
46194626
// TODO Delete this function, all GetTemplates should be authorized. For now just call getTemplates on the authz querier.
46204627
returnq.GetTemplatesWithFilter(ctx,arg)

‎coderd/database/dbauthz/dbauthz_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4065,6 +4065,19 @@ func (s *MethodTestSuite) TestSystemFunctions() {
40654065
s.Run("InsertWorkspaceAppStats",s.Subtest(func(db database.Store,check*expects) {
40664066
check.Args(database.InsertWorkspaceAppStatsParams{}).Asserts(rbac.ResourceSystem,policy.ActionCreate)
40674067
}))
4068+
s.Run("UpsertWorkspaceAppAuditSession",s.Subtest(func(db database.Store,check*expects) {
4069+
u:=dbgen.User(s.T(),db, database.User{})
4070+
pj:=dbgen.ProvisionerJob(s.T(),db,nil, database.ProvisionerJob{})
4071+
res:=dbgen.WorkspaceResource(s.T(),db, database.WorkspaceResource{JobID:pj.ID})
4072+
agent:=dbgen.WorkspaceAgent(s.T(),db, database.WorkspaceAgent{ResourceID:res.ID})
4073+
app:=dbgen.WorkspaceApp(s.T(),db, database.WorkspaceApp{AgentID:agent.ID})
4074+
check.Args(database.UpsertWorkspaceAppAuditSessionParams{
4075+
AgentID:agent.ID,
4076+
AppID:app.ID,
4077+
UserID:u.ID,
4078+
Ip:"127.0.0.1",
4079+
}).Asserts(rbac.ResourceSystem,policy.ActionUpdate)
4080+
}))
40684081
s.Run("InsertWorkspaceAgentScriptTimings",s.Subtest(func(db database.Store,check*expects) {
40694082
dbtestutil.DisableForeignKeysAndTriggers(s.T(),db)
40704083
check.Args(database.InsertWorkspaceAgentScriptTimingsParams{

‎coderd/database/dbmem/dbmem.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ func New() database.Store {
9292
workspaceAgentLogs:make([]database.WorkspaceAgentLog,0),
9393
workspaceBuilds:make([]database.WorkspaceBuild,0),
9494
workspaceApps:make([]database.WorkspaceApp,0),
95+
workspaceAppAuditSessions:make([]database.WorkspaceAppAuditSession,0),
9596
workspaces:make([]database.WorkspaceTable,0),
9697
workspaceProxies:make([]database.WorkspaceProxy,0),
9798
},
@@ -237,6 +238,7 @@ type data struct {
237238
workspaceAgentMemoryResourceMonitors []database.WorkspaceAgentMemoryResourceMonitor
238239
workspaceAgentVolumeResourceMonitors []database.WorkspaceAgentVolumeResourceMonitor
239240
workspaceApps []database.WorkspaceApp
241+
workspaceAppAuditSessions []database.WorkspaceAppAuditSession
240242
workspaceAppStatsLastInsertIDint64
241243
workspaceAppStats []database.WorkspaceAppStat
242244
workspaceBuilds []database.WorkspaceBuild
@@ -12281,6 +12283,63 @@ func (q *FakeQuerier) UpsertWorkspaceAgentPortShare(_ context.Context, arg datab
1228112283
returnpsl,nil
1228212284
}
1228312285

12286+
func (q*FakeQuerier)UpsertWorkspaceAppAuditSession(_ context.Context,arg database.UpsertWorkspaceAppAuditSessionParams) (time.Time,error) {
12287+
err:=validateDatabaseType(arg)
12288+
iferr!=nil {
12289+
return time.Time{},err
12290+
}
12291+
12292+
q.mutex.Lock()
12293+
deferq.mutex.Unlock()
12294+
12295+
fori,s:=rangeq.workspaceAppAuditSessions {
12296+
ifs.AgentID!=arg.AgentID {
12297+
continue
12298+
}
12299+
ifs.AppID!=arg.AppID {
12300+
continue
12301+
}
12302+
ifs.UserID!=arg.UserID {
12303+
continue
12304+
}
12305+
ifs.Ip!=arg.Ip {
12306+
continue
12307+
}
12308+
ifs.UserAgent!=arg.UserAgent {
12309+
continue
12310+
}
12311+
ifs.SlugOrPort!=arg.SlugOrPort {
12312+
continue
12313+
}
12314+
ifs.StatusCode!=arg.StatusCode {
12315+
continue
12316+
}
12317+
12318+
staleTime:=dbtime.Now().Add(-(time.Duration(arg.StaleIntervalMS)*time.Millisecond))
12319+
fresh:=s.UpdatedAt.After(staleTime)
12320+
12321+
q.workspaceAppAuditSessions[i].UpdatedAt=arg.UpdatedAt
12322+
if!fresh {
12323+
q.workspaceAppAuditSessions[i].StartedAt=arg.StartedAt
12324+
returnarg.StartedAt,nil
12325+
}
12326+
returns.StartedAt,nil
12327+
}
12328+
12329+
q.workspaceAppAuditSessions=append(q.workspaceAppAuditSessions, database.WorkspaceAppAuditSession{
12330+
AgentID:arg.AgentID,
12331+
AppID:arg.AppID,
12332+
UserID:arg.UserID,
12333+
Ip:arg.Ip,
12334+
UserAgent:arg.UserAgent,
12335+
SlugOrPort:arg.SlugOrPort,
12336+
StatusCode:arg.StatusCode,
12337+
StartedAt:arg.StartedAt,
12338+
UpdatedAt:arg.UpdatedAt,
12339+
})
12340+
returnarg.StartedAt,nil
12341+
}
12342+
1228412343
func (q*FakeQuerier)GetAuthorizedTemplates(ctx context.Context,arg database.GetTemplatesWithFilterParams,prepared rbac.PreparedAuthorized) ([]database.Template,error) {
1228512344
iferr:=validateDatabaseType(arg);err!=nil {
1228612345
returnnil,err

‎coderd/database/dbmetrics/querymetrics.go

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/dbmock/dbmock.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/dump.sql

Lines changed: 42 additions & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.

‎coderd/database/foreign_key_constraint.go

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more aboutcustomizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROPTABLE workspace_app_audit_sessions;

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp