Skip to content

Commit f44c89d

Browse files
authored
chore: enforce orgid in audit logs where required (#12283)
* chore: enforce orgid in audit logs where required
1 parent 74b749b commit f44c89d

File tree

6 files changed

+154
-80
lines changed

6 files changed

+154
-80
lines changed

coderd/audit/request.go

Lines changed: 58 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"database/sql"
66
"encoding/json"
7+
"flag"
78
"fmt"
89
"net"
910
"net/http"
@@ -25,6 +26,9 @@ type RequestParams struct {
2526
Audit Auditor
2627
Log slog.Logger
2728

29+
// OrganizationID is only provided when possible. If an audit resource extends
30+
// beyond the org scope, leave this as the nil uuid.
31+
OrganizationID uuid.UUID
2832
Request *http.Request
2933
Action database.AuditAction
3034
AdditionalFields json.RawMessage
@@ -96,7 +100,7 @@ func ResourceTarget[T Auditable](tgt T) string {
96100
case database.HealthSettings:
97101
return "" // no target?
98102
default:
99-
panic(fmt.Sprintf("unknown resource %T", tgt))
103+
panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt))
100104
}
101105
}
102106

@@ -129,7 +133,7 @@ func ResourceID[T Auditable](tgt T) uuid.UUID {
129133
// Artificial ID for auditing purposes
130134
return typed.ID
131135
default:
132-
panic(fmt.Sprintf("unknown resource %T", tgt))
136+
panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt))
133137
}
134138
}
135139

@@ -160,8 +164,57 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
160164
case database.HealthSettings:
161165
return database.ResourceTypeHealthSettings
162166
default:
163-
panic(fmt.Sprintf("unknown resource %T", typed))
167+
panic(fmt.Sprintf("unknown resource %T for ResourceType", typed))
168+
}
169+
}
170+
171+
// ResourceRequiresOrgID will ensure given resources are always audited with an
172+
// organization ID.
173+
func ResourceRequiresOrgID[T Auditable]() bool {
174+
var tgt T
175+
switch any(tgt).(type) {
176+
case database.Template, database.TemplateVersion:
177+
return true
178+
case database.Workspace, database.WorkspaceBuild:
179+
return true
180+
case database.AuditableGroup:
181+
return true
182+
case database.User:
183+
return false
184+
case database.GitSSHKey:
185+
return false
186+
case database.APIKey:
187+
return false
188+
case database.License:
189+
return false
190+
case database.WorkspaceProxy:
191+
return false
192+
case database.AuditOAuthConvertState:
193+
// The merge state is for the given user
194+
return false
195+
case database.HealthSettings:
196+
// Artificial ID for auditing purposes
197+
return false
198+
default:
199+
panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt))
200+
}
201+
}
202+
203+
// requireOrgID will either panic (in unit tests) or log an error (in production)
204+
// if the given resource requires an organization ID and the provided ID is nil.
205+
func requireOrgID[T Auditable](ctx context.Context, id uuid.UUID, log slog.Logger) uuid.UUID {
206+
if ResourceRequiresOrgID[T]() && id == uuid.Nil {
207+
var tgt T
208+
resourceName := fmt.Sprintf("%T", tgt)
209+
if flag.Lookup("test.v") != nil {
210+
// In unit tests we panic to fail the tests
211+
panic(fmt.Sprintf("missing required organization ID for resource %q", resourceName))
212+
}
213+
log.Error(ctx, "missing required organization ID for resource in audit log",
214+
slog.F("resource", resourceName),
215+
)
164216
}
217+
return id
165218
}
166219

167220
// InitRequest initializes an audit log for a request. It returns a function
@@ -243,6 +296,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
243296
StatusCode: int32(sw.Status),
244297
RequestID: httpmw.RequestID(p.Request),
245298
AdditionalFields: p.AdditionalFields,
299+
OrganizationID: requireOrgID[T](logCtx, p.OrganizationID, p.Log),
246300
}
247301
err := p.Audit.Export(ctx, auditLog)
248302
if err != nil {
@@ -276,7 +330,7 @@ func BackgroundAudit[T Auditable](ctx context.Context, p *BackgroundAuditParams[
276330
ID: uuid.New(),
277331
Time: dbtime.Now(),
278332
UserID: p.UserID,
279-
OrganizationID: p.OrganizationID,
333+
OrganizationID: requireOrgID[T](ctx, p.OrganizationID, p.Log),
280334
Ip: ip,
281335
UserAgent: sql.NullString{},
282336
ResourceType: either(p.Old, p.New, ResourceType[T], p.Action),

coderd/templates.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,11 @@ func (api *API) deleteTemplate(rw http.ResponseWriter, r *http.Request) {
5757
template = httpmw.TemplateParam(r)
5858
auditor = *api.Auditor.Load()
5959
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
60-
Audit: auditor,
61-
Log: api.Logger,
62-
Request: r,
63-
Action: database.AuditActionDelete,
60+
Audit: auditor,
61+
Log: api.Logger,
62+
Request: r,
63+
Action: database.AuditActionDelete,
64+
OrganizationID: template.OrganizationID,
6465
})
6566
)
6667
defer commitAudit()
@@ -123,16 +124,18 @@ func (api *API) postTemplateByOrganization(rw http.ResponseWriter, r *http.Reque
123124
apiKey = httpmw.APIKey(r)
124125
auditor = *api.Auditor.Load()
125126
templateAudit, commitTemplateAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
126-
Audit: auditor,
127-
Log: api.Logger,
128-
Request: r,
129-
Action: database.AuditActionCreate,
127+
Audit: auditor,
128+
Log: api.Logger,
129+
Request: r,
130+
Action: database.AuditActionCreate,
131+
OrganizationID: organization.ID,
130132
})
131133
templateVersionAudit, commitTemplateVersionAudit = audit.InitRequest[database.TemplateVersion](rw, &audit.RequestParams{
132-
Audit: auditor,
133-
Log: api.Logger,
134-
Request: r,
135-
Action: database.AuditActionWrite,
134+
Audit: auditor,
135+
Log: api.Logger,
136+
Request: r,
137+
Action: database.AuditActionWrite,
138+
OrganizationID: organization.ID,
136139
})
137140
)
138141
defer commitTemplateAudit()
@@ -542,10 +545,11 @@ func (api *API) patchTemplateMeta(rw http.ResponseWriter, r *http.Request) {
542545
auditor = *api.Auditor.Load()
543546
portSharer = *api.PortSharer.Load()
544547
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
545-
Audit: auditor,
546-
Log: api.Logger,
547-
Request: r,
548-
Action: database.AuditActionWrite,
548+
Audit: auditor,
549+
Log: api.Logger,
550+
Request: r,
551+
Action: database.AuditActionWrite,
552+
OrganizationID: template.OrganizationID,
549553
})
550554
)
551555
defer commitAudit()

coderd/templateversions.go

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,10 +1040,11 @@ func (api *API) postArchiveTemplateVersions(rw http.ResponseWriter, r *http.Requ
10401040
template = httpmw.TemplateParam(r)
10411041
auditor = *api.Auditor.Load()
10421042
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
1043-
Audit: auditor,
1044-
Log: api.Logger,
1045-
Request: r,
1046-
Action: database.AuditActionWrite,
1043+
Audit: auditor,
1044+
Log: api.Logger,
1045+
Request: r,
1046+
Action: database.AuditActionWrite,
1047+
OrganizationID: template.OrganizationID,
10471048
})
10481049
)
10491050
defer commitAudit()
@@ -1122,10 +1123,11 @@ func (api *API) setArchiveTemplateVersion(archive bool) func(rw http.ResponseWri
11221123
templateVersion = httpmw.TemplateVersionParam(r)
11231124
auditor = *api.Auditor.Load()
11241125
aReq, commitAudit = audit.InitRequest[database.TemplateVersion](rw, &audit.RequestParams{
1125-
Audit: auditor,
1126-
Log: api.Logger,
1127-
Request: r,
1128-
Action: database.AuditActionWrite,
1126+
Audit: auditor,
1127+
Log: api.Logger,
1128+
Request: r,
1129+
Action: database.AuditActionWrite,
1130+
OrganizationID: templateVersion.OrganizationID,
11291131
})
11301132
)
11311133
defer commitAudit()
@@ -1207,10 +1209,11 @@ func (api *API) patchActiveTemplateVersion(rw http.ResponseWriter, r *http.Reque
12071209
template = httpmw.TemplateParam(r)
12081210
auditor = *api.Auditor.Load()
12091211
aReq, commitAudit = audit.InitRequest[database.Template](rw, &audit.RequestParams{
1210-
Audit: auditor,
1211-
Log: api.Logger,
1212-
Request: r,
1213-
Action: database.AuditActionWrite,
1212+
Audit: auditor,
1213+
Log: api.Logger,
1214+
Request: r,
1215+
Action: database.AuditActionWrite,
1216+
OrganizationID: template.OrganizationID,
12141217
})
12151218
)
12161219
defer commitAudit()
@@ -1310,10 +1313,11 @@ func (api *API) postTemplateVersionsByOrganization(rw http.ResponseWriter, r *ht
13101313
organization = httpmw.OrganizationParam(r)
13111314
auditor = *api.Auditor.Load()
13121315
aReq, commitAudit = audit.InitRequest[database.TemplateVersion](rw, &audit.RequestParams{
1313-
Audit: auditor,
1314-
Log: api.Logger,
1315-
Request: r,
1316-
Action: database.AuditActionCreate,
1316+
Audit: auditor,
1317+
Log: api.Logger,
1318+
Request: r,
1319+
Action: database.AuditActionCreate,
1320+
OrganizationID: organization.ID,
13171321
})
13181322

13191323
req codersdk.CreateTemplateVersionRequest

coderd/workspaces.go

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
345345
Request: r,
346346
Action: database.AuditActionCreate,
347347
AdditionalFields: wriBytes,
348+
OrganizationID: organization.ID,
348349
})
349350

350351
defer commitAudit()
@@ -644,10 +645,11 @@ func (api *API) patchWorkspace(rw http.ResponseWriter, r *http.Request) {
644645
workspace = httpmw.WorkspaceParam(r)
645646
auditor = api.Auditor.Load()
646647
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
647-
Audit: *auditor,
648-
Log: api.Logger,
649-
Request: r,
650-
Action: database.AuditActionWrite,
648+
Audit: *auditor,
649+
Log: api.Logger,
650+
Request: r,
651+
Action: database.AuditActionWrite,
652+
OrganizationID: workspace.OrganizationID,
651653
})
652654
)
653655
defer commitAudit()
@@ -734,10 +736,11 @@ func (api *API) putWorkspaceAutostart(rw http.ResponseWriter, r *http.Request) {
734736
workspace = httpmw.WorkspaceParam(r)
735737
auditor = api.Auditor.Load()
736738
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
737-
Audit: *auditor,
738-
Log: api.Logger,
739-
Request: r,
740-
Action: database.AuditActionWrite,
739+
Audit: *auditor,
740+
Log: api.Logger,
741+
Request: r,
742+
Action: database.AuditActionWrite,
743+
OrganizationID: workspace.OrganizationID,
741744
})
742745
)
743746
defer commitAudit()
@@ -808,10 +811,11 @@ func (api *API) putWorkspaceTTL(rw http.ResponseWriter, r *http.Request) {
808811
workspace = httpmw.WorkspaceParam(r)
809812
auditor = api.Auditor.Load()
810813
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
811-
Audit: *auditor,
812-
Log: api.Logger,
813-
Request: r,
814-
Action: database.AuditActionWrite,
814+
Audit: *auditor,
815+
Log: api.Logger,
816+
Request: r,
817+
Action: database.AuditActionWrite,
818+
OrganizationID: workspace.OrganizationID,
815819
})
816820
)
817821
defer commitAudit()
@@ -896,10 +900,11 @@ func (api *API) putWorkspaceDormant(rw http.ResponseWriter, r *http.Request) {
896900
oldWorkspace = workspace
897901
auditor = api.Auditor.Load()
898902
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
899-
Audit: *auditor,
900-
Log: api.Logger,
901-
Request: r,
902-
Action: database.AuditActionWrite,
903+
Audit: *auditor,
904+
Log: api.Logger,
905+
Request: r,
906+
Action: database.AuditActionWrite,
907+
OrganizationID: workspace.OrganizationID,
903908
})
904909
)
905910
aReq.Old = oldWorkspace
@@ -1094,10 +1099,11 @@ func (api *API) putFavoriteWorkspace(rw http.ResponseWriter, r *http.Request) {
10941099
}
10951100

10961101
aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
1097-
Audit: *auditor,
1098-
Log: api.Logger,
1099-
Request: r,
1100-
Action: database.AuditActionWrite,
1102+
Audit: *auditor,
1103+
Log: api.Logger,
1104+
Request: r,
1105+
Action: database.AuditActionWrite,
1106+
OrganizationID: workspace.OrganizationID,
11011107
})
11021108
defer commitAudit()
11031109
aReq.Old = workspace
@@ -1140,10 +1146,11 @@ func (api *API) deleteFavoriteWorkspace(rw http.ResponseWriter, r *http.Request)
11401146
}
11411147

11421148
aReq, commitAudit := audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
1143-
Audit: *auditor,
1144-
Log: api.Logger,
1145-
Request: r,
1146-
Action: database.AuditActionWrite,
1149+
Audit: *auditor,
1150+
Log: api.Logger,
1151+
Request: r,
1152+
Action: database.AuditActionWrite,
1153+
OrganizationID: workspace.OrganizationID,
11471154
})
11481155

11491156
defer commitAudit()
@@ -1178,10 +1185,11 @@ func (api *API) putWorkspaceAutoupdates(rw http.ResponseWriter, r *http.Request)
11781185
workspace = httpmw.WorkspaceParam(r)
11791186
auditor = api.Auditor.Load()
11801187
aReq, commitAudit = audit.InitRequest[database.Workspace](rw, &audit.RequestParams{
1181-
Audit: *auditor,
1182-
Log: api.Logger,
1183-
Request: r,
1184-
Action: database.AuditActionWrite,
1188+
Audit: *auditor,
1189+
Log: api.Logger,
1190+
Request: r,
1191+
Action: database.AuditActionWrite,
1192+
OrganizationID: workspace.OrganizationID,
11851193
})
11861194
)
11871195
defer commitAudit()

0 commit comments

Comments
 (0)