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

Commit514d5b5

Browse files
committed
feat(enterprise): add auditing to SCIM
1 parenta1db6d8 commit514d5b5

File tree

7 files changed

+138
-18
lines changed

7 files changed

+138
-18
lines changed

‎coderd/audit/request.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http"
1111
"strconv"
1212

13+
"github.com/davecgh/go-spew/spew"
1314
"github.com/google/uuid"
1415
"github.com/sqlc-dev/pqtype"
1516
"go.opentelemetry.io/otel/baggage"
@@ -31,7 +32,7 @@ type RequestParams struct {
3132
OrganizationID uuid.UUID
3233
Request*http.Request
3334
Action database.AuditAction
34-
AdditionalFieldsjson.RawMessage
35+
AdditionalFieldsinterface{}
3536
}
3637

3738
typeRequest[TAuditable]struct {
@@ -274,6 +275,7 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
274275
ifsw.Status<400&&
275276
req.params.Action!=database.AuditActionLogin&&req.params.Action!=database.AuditActionLogout {
276277
diff:=Diff(p.Audit,req.Old,req.New)
278+
fmt.Println("DIFFF",diff)
277279

278280
varerrerror
279281
diffRaw,err=json.Marshal(diff)
@@ -283,8 +285,15 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
283285
}
284286
}
285287

286-
ifp.AdditionalFields==nil {
287-
p.AdditionalFields=json.RawMessage("{}")
288+
additionalFieldsRaw:=json.RawMessage("{}")
289+
290+
ifp.AdditionalFields!=nil {
291+
data,err:=json.Marshal(p.AdditionalFields)
292+
iferr!=nil {
293+
p.Log.Warn(logCtx,"marshal additional fields",slog.Error(err))
294+
}else {
295+
additionalFieldsRaw=json.RawMessage(data)
296+
}
288297
}
289298

290299
varuserID uuid.UUID
@@ -319,9 +328,11 @@ func InitRequest[T Auditable](w http.ResponseWriter, p *RequestParams) (*Request
319328
Diff:diffRaw,
320329
StatusCode:int32(sw.Status),
321330
RequestID:httpmw.RequestID(p.Request),
322-
AdditionalFields:p.AdditionalFields,
331+
AdditionalFields:additionalFieldsRaw,
323332
OrganizationID:requireOrgID[T](logCtx,p.OrganizationID,p.Log),
324333
}
334+
fmt.Println("export")
335+
spew.Dump(auditLog)
325336
err:=p.Audit.Export(ctx,auditLog)
326337
iferr!=nil {
327338
p.Log.Error(logCtx,"export audit log",

‎coderd/workspaces.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -361,17 +361,12 @@ func (api *API) postWorkspacesByOrganization(rw http.ResponseWriter, r *http.Req
361361
}
362362
)
363363

364-
wriBytes,err:=json.Marshal(workspaceResourceInfo)
365-
iferr!=nil {
366-
api.Logger.Warn(ctx,"marshal workspace owner name")
367-
}
368-
369364
aReq,commitAudit:=audit.InitRequest[database.Workspace](rw,&audit.RequestParams{
370365
Audit:*auditor,
371366
Log:api.Logger,
372367
Request:r,
373368
Action:database.AuditActionCreate,
374-
AdditionalFields:wriBytes,
369+
AdditionalFields:workspaceResourceInfo,
375370
OrganizationID:organization.ID,
376371
})
377372

‎enterprise/coderd/scim.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,16 @@ import (
1515
"golang.org/x/xerrors"
1616

1717
agpl"github.com/coder/coder/v2/coderd"
18+
"github.com/coder/coder/v2/coderd/audit"
1819
"github.com/coder/coder/v2/coderd/database"
1920
"github.com/coder/coder/v2/coderd/database/dbauthz"
2021
"github.com/coder/coder/v2/coderd/database/dbtime"
2122
"github.com/coder/coder/v2/coderd/httpapi"
2223
"github.com/coder/coder/v2/codersdk"
2324
)
2425

26+
varSCIMAuditUserID=uuid.MustParse("1f688bd1-8d6a-4a17-ae93-d0761f3b0a09")
27+
2528
func (api*API)scimEnabledMW(next http.Handler) http.Handler {
2629
returnhttp.HandlerFunc(func(rw http.ResponseWriter,r*http.Request) {
2730
api.entitlementsMu.RLock()
@@ -118,6 +121,11 @@ type SCIMUser struct {
118121
}`json:"meta"`
119122
}
120123

124+
varSCIMAuditAdditionalFields=map[string]string{
125+
"automatic_actor":"coder",
126+
"automatic_subsystem":"scim",
127+
}
128+
121129
// scimPostUser creates a new user, or returns the existing user if it exists.
122130
//
123131
// @Summary SCIM 2.0: Create new user
@@ -135,6 +143,16 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
135143
return
136144
}
137145

146+
auditor:=*api.AGPL.Auditor.Load()
147+
aReq,commitAudit:=audit.InitRequest[database.User](rw,&audit.RequestParams{
148+
Audit:auditor,
149+
Log:api.Logger,
150+
Request:r,
151+
Action:database.AuditActionCreate,
152+
AdditionalFields:SCIMAuditAdditionalFields,
153+
})
154+
defercommitAudit()
155+
138156
varsUserSCIMUser
139157
err:=json.NewDecoder(r.Body).Decode(&sUser)
140158
iferr!=nil {
@@ -170,7 +188,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
170188

171189
ifsUser.Active&&dbUser.Status==database.UserStatusSuspended {
172190
//nolint:gocritic
173-
_,err=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
191+
newUser,err:=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
174192
ID:dbUser.ID,
175193
// The user will get transitioned to Active after logging in.
176194
Status:database.UserStatusDormant,
@@ -180,8 +198,13 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
180198
_=handlerutil.WriteError(rw,err)
181199
return
182200
}
201+
aReq.New=newUser
202+
}else {
203+
aReq.New=dbUser
183204
}
184205

206+
aReq.Old=dbUser
207+
185208
httpapi.Write(ctx,rw,http.StatusOK,sUser)
186209
return
187210
}
@@ -223,6 +246,8 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
223246
_=handlerutil.WriteError(rw,err)
224247
return
225248
}
249+
aReq.New=dbUser
250+
aReq.UserID=dbUser.ID
226251

227252
sUser.ID=dbUser.ID.String()
228253
sUser.UserName=dbUser.Username
@@ -248,6 +273,15 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
248273
return
249274
}
250275

276+
auditor:=*api.AGPL.Auditor.Load()
277+
aReq,commitAudit:=audit.InitRequest[database.User](rw,&audit.RequestParams{
278+
Audit:auditor,
279+
Log:api.Logger,
280+
Request:r,
281+
Action:database.AuditActionWrite,
282+
})
283+
defercommitAudit()
284+
251285
id:=chi.URLParam(r,"id")
252286

253287
varsUserSCIMUser
@@ -270,6 +304,8 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
270304
_=handlerutil.WriteError(rw,err)
271305
return
272306
}
307+
aReq.Old=dbUser
308+
aReq.UserID=dbUser.ID
273309

274310
varstatus database.UserStatus
275311
ifsUser.Active {
@@ -280,7 +316,7 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
280316
}
281317

282318
//nolint:gocritic // needed for SCIM
283-
_,err=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
319+
userNew,err:=api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
284320
ID:dbUser.ID,
285321
Status:status,
286322
UpdatedAt:dbtime.Now(),
@@ -289,6 +325,7 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
289325
_=handlerutil.WriteError(rw,err)
290326
return
291327
}
328+
aReq.New=userNew
292329

293330
httpapi.Write(ctx,rw,http.StatusOK,sUser)
294331
}

‎enterprise/coderd/scim_test.go

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ import (
1111
"github.com/stretchr/testify/assert"
1212
"github.com/stretchr/testify/require"
1313

14+
"github.com/coder/coder/v2/coderd/audit"
15+
"github.com/coder/coder/v2/coderd/coderdtest"
16+
"github.com/coder/coder/v2/coderd/database"
1417
"github.com/coder/coder/v2/codersdk"
1518
"github.com/coder/coder/v2/cryptorand"
1619
"github.com/coder/coder/v2/enterprise/coderd"
@@ -109,21 +112,34 @@ func TestScim(t *testing.T) {
109112
defercancel()
110113

111114
scimAPIKey:= []byte("hi")
115+
mockAudit:=audit.NewMock()
112116
client,_:=coderdenttest.New(t,&coderdenttest.Options{
113-
SCIMAPIKey:scimAPIKey,
117+
Options:&coderdtest.Options{Auditor:mockAudit},
118+
SCIMAPIKey:scimAPIKey,
119+
AuditLogging:true,
114120
LicenseOptions:&coderdenttest.LicenseOptions{
115121
AccountID:"coolin",
116122
Features: license.Features{
117-
codersdk.FeatureSCIM:1,
123+
codersdk.FeatureSCIM:1,
124+
codersdk.FeatureAuditLog:1,
118125
},
119126
},
120127
})
128+
mockAudit.ResetLogs()
121129

122130
sUser:=makeScimUser(t)
123131
res,err:=client.Request(ctx,"POST","/scim/v2/Users",sUser,setScimAuth(scimAPIKey))
124132
require.NoError(t,err)
125133
deferres.Body.Close()
126-
assert.Equal(t,http.StatusOK,res.StatusCode)
134+
require.Equal(t,http.StatusOK,res.StatusCode)
135+
136+
aLogs:=mockAudit.AuditLogs()
137+
require.Len(t,aLogs,1)
138+
af:=map[string]string{}
139+
err=json.Unmarshal([]byte(aLogs[0].AdditionalFields),&af)
140+
require.NoError(t,err)
141+
assert.Equal(t,coderd.SCIMAuditAdditionalFields,af)
142+
assert.Equal(t,database.AuditActionCreate,aLogs[0].Action)
127143

128144
userRes,err:=client.Users(ctx, codersdk.UsersRequest{Search:sUser.Emails[0].Value})
129145
require.NoError(t,err)
@@ -306,21 +322,27 @@ func TestScim(t *testing.T) {
306322
defercancel()
307323

308324
scimAPIKey:= []byte("hi")
325+
mockAudit:=audit.NewMock()
309326
client,_:=coderdenttest.New(t,&coderdenttest.Options{
310-
SCIMAPIKey:scimAPIKey,
327+
Options:&coderdtest.Options{Auditor:mockAudit},
328+
SCIMAPIKey:scimAPIKey,
329+
AuditLogging:true,
311330
LicenseOptions:&coderdenttest.LicenseOptions{
312331
AccountID:"coolin",
313332
Features: license.Features{
314-
codersdk.FeatureSCIM:1,
333+
codersdk.FeatureSCIM:1,
334+
codersdk.FeatureAuditLog:1,
315335
},
316336
},
317337
})
338+
mockAudit.ResetLogs()
318339

319340
sUser:=makeScimUser(t)
320341
res,err:=client.Request(ctx,"POST","/scim/v2/Users",sUser,setScimAuth(scimAPIKey))
321342
require.NoError(t,err)
322343
deferres.Body.Close()
323344
assert.Equal(t,http.StatusOK,res.StatusCode)
345+
mockAudit.ResetLogs()
324346

325347
err=json.NewDecoder(res.Body).Decode(&sUser)
326348
require.NoError(t,err)
@@ -333,6 +355,10 @@ func TestScim(t *testing.T) {
333355
_=res.Body.Close()
334356
assert.Equal(t,http.StatusOK,res.StatusCode)
335357

358+
aLogs:=mockAudit.AuditLogs()
359+
require.Len(t,aLogs,1)
360+
assert.Equal(t,database.AuditActionWrite,aLogs[0].Action)
361+
336362
userRes,err:=client.Users(ctx, codersdk.UsersRequest{Search:sUser.Emails[0].Value})
337363
require.NoError(t,err)
338364
require.Len(t,userRes.Users,1)

‎site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.stories.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,45 @@ export const UnsuccessfulLoginForUnknownUser: Story = {
5656
auditLog:MockAuditLogUnsuccessfulLoginKnownUser,
5757
},
5858
};
59+
60+
exportconstCreateUser:Story={
61+
args:{
62+
auditLog:{
63+
...MockAuditLog,
64+
resource_type:"user",
65+
resource_target:"colin",
66+
description:"{user} created user {target}",
67+
},
68+
},
69+
};
70+
71+
exportconstSCIMCreateUser:Story={
72+
args:{
73+
auditLog:{
74+
...MockAuditLog,
75+
resource_type:"user",
76+
resource_target:"colin",
77+
description:"{user} created user {target}",
78+
additional_fields:{
79+
automatic_actor:"coder",
80+
automatic_subsystem:"scim",
81+
},
82+
},
83+
},
84+
};
85+
86+
exportconstSCIMUpdateUser:Story={
87+
args:{
88+
auditLog:{
89+
...MockAuditLog,
90+
action:"write",
91+
resource_type:"user",
92+
resource_target:"colin",
93+
description:"{user} updated user {target}",
94+
additional_fields:{
95+
automatic_actor:"coder",
96+
automatic_subsystem:"scim",
97+
},
98+
},
99+
},
100+
};

‎site/src/pages/AuditPage/AuditLogRow/AuditLogDescription/AuditLogDescription.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
1212
auditLog,
1313
})=>{
1414
lettarget=auditLog.resource_target.trim();
15-
constuser=auditLog.user?.username.trim();
15+
letuser=auditLog.user?.username.trim();
1616

1717
if(auditLog.resource_type==="workspace_build"){
1818
return<BuildAuditDescriptionauditLog={auditLog}/>;
@@ -23,6 +23,14 @@ export const AuditLogDescription: FC<AuditLogDescriptionProps> = ({
2323
target="";
2424
}
2525

26+
// This occurs when SCIM creates a user.
27+
if(
28+
auditLog.resource_type==="user"&&
29+
auditLog.additional_fields?.automatic_actor==="coder"
30+
){
31+
user="Coder automatically";
32+
}
33+
2634
consttruncatedDescription=auditLog.description
2735
.replace("{user}",`${user}`)
2836
.replace("{target}","");

‎site/src/testHelpers/entities.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2285,6 +2285,7 @@ export const MockWorkspaceQuota: TypesGen.WorkspaceQuota = {
22852285
budget:100,
22862286
};
22872287

2288+
22882289
exportconstMockGroup:TypesGen.Group={
22892290
id:"fbd2116a-8961-4954-87ae-e4575bd29ce0",
22902291
name:"Front-End",

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp