Skip to content

Commit 4bc53bb

Browse files
committed
chore: enforce orgid in audit logs where required
1 parent 66585f0 commit 4bc53bb

File tree

1 file changed

+49
-3
lines changed

1 file changed

+49
-3
lines changed

coderd/audit/request.go

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ type RequestParams struct {
2525
Audit Auditor
2626
Log slog.Logger
2727

28+
// OrganizationID is only provided when possible. If an audit resource extends
29+
// beyond the org scope, leave this as the nil uuid.
30+
OrganizationID uuid.UUID
2831
Request *http.Request
2932
Action database.AuditAction
3033
AdditionalFields json.RawMessage
@@ -96,7 +99,7 @@ func ResourceTarget[T Auditable](tgt T) string {
9699
case database.HealthSettings:
97100
return "" // no target?
98101
default:
99-
panic(fmt.Sprintf("unknown resource %T", tgt))
102+
panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt))
100103
}
101104
}
102105

@@ -129,7 +132,7 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
129132
// Artificial ID for auditing purposes
130133
return typed.ID
131134
default:
132-
panic(fmt.Sprintf("unknown resource %T", tgt))
135+
panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt))
133136
}
134137
}
135138

@@ -160,7 +163,39 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
160163
case database.HealthSettings:
161164
return database.ResourceTypeHealthSettings
162165
default:
163-
panic(fmt.Sprintf("unknown resource %T", typed))
166+
panic(fmt.Sprintf("unknown resource %T for ResourceType", typed))
167+
}
168+
}
169+
170+
// ResourceRequiresOrgID will ensure given resources are always audited with an
171+
// organization ID.
172+
func ResourceRequiresOrgID[T Auditable]() bool {
173+
var tgt T
174+
switch any(tgt).(type) {
175+
case database.Template, database.TemplateVersion:
176+
return true
177+
case database.Workspace, database.WorkspaceBuild:
178+
return true
179+
case database.AuditableGroup:
180+
return true
181+
case database.User:
182+
return false
183+
case database.GitSSHKey:
184+
return false
185+
case database.APIKey:
186+
return false
187+
case database.License:
188+
return false
189+
case database.WorkspaceProxy:
190+
return false
191+
case database.AuditOAuthConvertState:
192+
// The merge state is for the given user
193+
return false
194+
case database.HealthSettings:
195+
// Artificial ID for auditing purposes
196+
return false
197+
default:
198+
panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt))
164199
}
165200
}
166201

@@ -228,6 +263,11 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
228263
action = req.Action
229264
}
230265

266+
if ResourceRequiresOrgID[T]() && p.OrganizationID == uuid.Nil {
267+
// We panic as this is a developer error and should never happen.
268+
panic(fmt.Sprintf("missing required organization ID for resource %s", either(req.Old, req.New, ResourceType[T], req.params.Action)))
269+
}
270+
231271
ip := parseIP(p.Request.RemoteAddr)
232272
auditLog := database.AuditLog{
233273
ID: uuid.New(),
@@ -243,6 +283,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
243283
StatusCode: int32(sw.Status),
244284
RequestID: httpmw.RequestID(p.Request),
245285
AdditionalFields: p.AdditionalFields,
286+
OrganizationID: p.OrganizationID,
246287
}
247288
err := p.Audit.Export(ctx, auditLog)
248289
if err != nil {
@@ -272,6 +313,11 @@ func BackgroundAudit[T Auditable](ctx context.Context, p *BackgroundAuditParams[
272313
p.AdditionalFields = json.RawMessage("{}")
273314
}
274315

316+
if ResourceRequiresOrgID[T]() && p.OrganizationID == uuid.Nil {
317+
// We panic as this is a developer error and should never happen.
318+
panic(fmt.Sprintf("missing required organization ID for resource %s", either(p.Old, p.New, ResourceType[T], p.Action))
319+
}
320+
275321
auditLog := database.AuditLog{
276322
ID: uuid.New(),
277323
Time: dbtime.Now(),

0 commit comments

Comments
 (0)