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

Commitaf2c645

Browse files
committed
refactor: add allow_list field to API keys for resource scoping
- Add allow_list field to CreateTokenRequest API and database schema- Implement APIKeyEffectiveScope that merges scopes with token allow_list- Create x/wildcard package for type-safe wildcard values- Add rbac.ParseAllowList for validating and normalizing allow lists- Support resource targeting like "workspace:*" or "template:<uuid>"- Default to wildcard (*:*) for backward compatibility
1 parent3d8007b commitaf2c645

File tree

21 files changed

+541
-38
lines changed

21 files changed

+541
-38
lines changed

‎coderd/apidoc/docs.go‎

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

‎coderd/apidoc/swagger.json‎

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

‎coderd/apikey.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,37 @@ func (api *API) postToken(rw http.ResponseWriter, r *http.Request) {
116116
TokenName:tokenName,
117117
}
118118

119+
iflen(createToken.AllowList)>0 {
120+
rbacAllowListElements:=make([]rbac.AllowListElement,0,len(createToken.AllowList))
121+
for_,t:=rangecreateToken.AllowList {
122+
entry,err:=rbac.NewAllowListElement(string(t.Type),t.ID)
123+
iferr!=nil {
124+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
125+
Message:"Failed to create API key.",
126+
Detail:err.Error(),
127+
})
128+
return
129+
}
130+
rbacAllowListElements=append(rbacAllowListElements,entry)
131+
}
132+
133+
rbacAllowList,err:=rbac.ProcessAllowList(rbacAllowListElements,128)
134+
iferr!=nil {
135+
httpapi.Write(ctx,rw,http.StatusBadRequest, codersdk.Response{
136+
Message:"Failed to create API key.",
137+
Detail:err.Error(),
138+
})
139+
return
140+
}
141+
142+
dbAllowList:=make(database.AllowList,0,len(rbacAllowList))
143+
for_,e:=rangerbacAllowList {
144+
dbAllowList=append(dbAllowList, database.AllowListTarget{Type:e.Type,ID:e.ID})
145+
}
146+
147+
params.AllowList=dbAllowList
148+
}
149+
119150
ifcreateToken.Lifetime!=0 {
120151
err:=api.validateAPIKeyLifetime(ctx,user.ID,createToken.Lifetime)
121152
iferr!=nil {

‎coderd/apikey/apikey.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ type CreateParams struct {
3434
Scopes database.APIKeyScopes
3535
TokenNamestring
3636
RemoteAddrstring
37+
// AllowList is an optional, normalized allow-list
38+
// of resource type and uuid entries. If empty, defaults to wildcard.
39+
AllowList database.AllowList
3740
}
3841

3942
// Generate generates an API key, returning the key as a string as well as the
@@ -115,7 +118,7 @@ func Generate(params CreateParams) (database.InsertAPIKeyParams, string, error)
115118
HashedSecret:hashed[:],
116119
LoginType:params.LoginType,
117120
Scopes:scopes,
118-
AllowList:database.AllowList{database.AllowListWildcard()},
121+
AllowList:params.AllowList,
119122
TokenName:params.TokenName,
120123
},token,nil
121124
}

‎coderd/database/dbauthz/setup_test.go‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,17 +225,20 @@ func (s *MethodTestSuite) SubtestWithDB(db database.Store, testCaseF func(db dat
225225
iftestCase.outputs!=nil {
226226
// Assert the required outputs
227227
s.Equal(len(testCase.outputs),len(outputs),"method %q returned unexpected number of outputs",methodName)
228+
cmpOptions:= []cmp.Option{
229+
// Equate nil and empty slices.
230+
cmpopts.EquateEmpty(),
231+
}
228232
fori:=rangeoutputs {
229233
a,b:=testCase.outputs[i].Interface(),outputs[i].Interface()
230234

231235
// To avoid the extra small overhead of gob encoding, we can
232236
// first check if the values are equal with regard to order.
233237
// If not, re-check disregarding order and show a nice diff
234238
// output of the two values.
235-
if!cmp.Equal(a,b,cmpopts.EquateEmpty()) {
236-
ifdiff:=cmp.Diff(a,b,
237-
// Equate nil and empty slices.
238-
cmpopts.EquateEmpty(),
239+
if!cmp.Equal(a,b,cmpOptions...) {
240+
diffOpts:=append(
241+
append([]cmp.Option{},cmpOptions...),
239242
// Allow slice order to be ignored.
240243
cmpopts.SortSlices(func(a,bany)bool {
241244
varab,bb strings.Builder
@@ -247,7 +250,8 @@ func (s *MethodTestSuite) SubtestWithDB(db database.Store, testCaseF func(db dat
247250
// https://github.com/google/go-cmp/issues/67
248251
returnab.String()<bb.String()
249252
}),
250-
);diff!="" {
253+
)
254+
ifdiff:=cmp.Diff(a,b,diffOpts...);diff!="" {
251255
s.Failf("compare outputs failed","method %q returned unexpected output %d (-want +got):\n%s",methodName,i,diff)
252256
}
253257
}

‎coderd/database/dbgen/dbgen.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ func APIKey(t testing.TB, db database.Store, seed database.APIKey, munge ...func
186186
UpdatedAt:takeFirst(seed.UpdatedAt,dbtime.Now()),
187187
LoginType:takeFirst(seed.LoginType,database.LoginTypePassword),
188188
Scopes:takeFirstSlice([]database.APIKeyScope(seed.Scopes), []database.APIKeyScope{database.ApiKeyScopeCoderAll}),
189-
AllowList:takeFirstSlice(seed.AllowList, database.AllowList{database.AllowListWildcard()}),
189+
AllowList:takeFirstSlice(seed.AllowList, database.AllowList{}),
190190
TokenName:takeFirst(seed.TokenName),
191191
}
192192
for_,fn:=rangemunge {

‎coderd/database/modelmethods.go‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,41 @@ func (s APIKeyScopes) Name() rbac.RoleIdentifier {
235235
return rbac.RoleIdentifier{Name:"scopes["+strings.Join(names,"+")+"]"}
236236
}
237237

238+
// APIKeyEffectiveScope merges expanded scopes with the API key's DB allow_list.
239+
// If the DB allow_list is a wildcard or empty, the merged scope's allow list is unchanged.
240+
// Otherwise, the DB allow_list overrides the merged AllowIDList to enforce the token's
241+
// resource scoping consistently across all permissions.
242+
typeAPIKeyEffectiveScopestruct {
243+
ScopesAPIKeyScopes
244+
AllowListAllowList
245+
}
246+
247+
func (eAPIKeyEffectiveScope)Name() rbac.RoleIdentifier {returne.Scopes.Name() }
248+
249+
func (eAPIKeyEffectiveScope)Expand() (rbac.Scope,error) {
250+
merged,err:=e.Scopes.Expand()
251+
iferr!=nil {
252+
return rbac.Scope{},err
253+
}
254+
iflen(e.AllowList)==0 {
255+
returnmerged,nil
256+
}
257+
258+
// If allow list contains a single wildcard (*:*), keep merged allow list as-is
259+
for_,entry:=rangee.AllowList {
260+
ifentry.Type==policy.WildcardSymbol&&entry.ID==policy.WildcardSymbol {
261+
returnmerged,nil
262+
}
263+
}
264+
265+
out:=make([]rbac.AllowListElement,0,len(e.AllowList))
266+
for_,t:=rangee.AllowList {
267+
out=append(out, rbac.AllowListElement{Type:t.Type,ID:t.ID})
268+
}
269+
merged.AllowIDList=out
270+
returnmerged,nil
271+
}
272+
238273
func (kAPIKey)RBACObject() rbac.Object {
239274
returnrbac.ResourceApiKey.WithIDString(k.ID).
240275
WithOwner(k.UserID.String())

‎coderd/database/types.go‎

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,7 @@ func (m StringMapOfInt) Value() (driver.Value, error) {
163163

164164
typeCustomRolePermissions []CustomRolePermission
165165

166-
// APIKeyScopes implements sql.Scanner and driver.Valuer so it can be read from
167-
// and written to the Postgres api_key_scope[] enum array column.
168-
func (s*APIKeyScopes)Scan(srcinterface{})error {
166+
func (s*APIKeyScopes)Scan(srcany)error {
169167
vararr []string
170168
iferr:=pq.Array(&arr).Scan(src);err!=nil {
171169
returnerr
@@ -323,22 +321,17 @@ type AllowListTarget struct {
323321
}
324322

325323
// String returns the canonical database representation "type:id".
326-
func (tAllowListTarget)String()string {
327-
returnt.Type+":"+t.ID
328-
}
324+
func (tAllowListTarget)String()string {returnt.Type+":"+t.ID }
329325

330326
// ParseAllowListTarget parses the canonical string form "type:id".
331327
funcParseAllowListTarget(sstring) (AllowListTarget,error) {
332-
targetType,id,ok:=rbac.ParseResourceAction(s)
333-
if!ok {
334-
returnAllowListTarget{},xerrors.Errorf("invalid allow list target: %q",s)
328+
elem,err:=rbac.ParseAllowListEntry(s)
329+
iferr!=nil {
330+
returnAllowListTarget{},err
335331
}
336-
returnAllowListTarget{Type:targetType,ID:id},nil
332+
returnAllowListTarget{Type:elem.Type,ID:elem.ID},nil
337333
}
338334

339-
// AllowListWildcard returns the wildcard allow-list entry {"*","*"}.
340-
funcAllowListWildcard()AllowListTarget {returnAllowListTarget{Type:"*",ID:"*"} }
341-
342335
// AllowList is a typed wrapper around a list of AllowListTarget entries.
343336
// It implements sql.Scanner and driver.Valuer so it can be stored in and
344337
// loaded from a Postgres text[] column that stores each entry in the

‎coderd/httpmw/apikey.go‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,10 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
434434
// If the key is valid, we also fetch the user roles and status.
435435
// The roles are used for RBAC authorize checks, and the status
436436
// is to block 'suspended' users from accessing the platform.
437-
actor,userStatus,err:=UserRBACSubject(ctx,cfg.DB,key.UserID,key.Scopes)
437+
actor,userStatus,err:=UserRBACSubject(ctx,cfg.DB,key.UserID, database.APIKeyEffectiveScope{
438+
Scopes:key.Scopes,
439+
AllowList:key.AllowList,
440+
})
438441
iferr!=nil {
439442
returnwrite(http.StatusUnauthorized, codersdk.Response{
440443
Message:internalErrorMessage,

‎coderd/httpmw/authorize_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ func addUser(t *testing.T, db database.Store, roles ...string) (database.User, s
173173
ExpiresAt:dbtime.Now().Add(time.Minute),
174174
LoginType:database.LoginTypePassword,
175175
Scopes: database.APIKeyScopes{database.ApiKeyScopeCoderAll},
176-
AllowList: database.AllowList{database.AllowListWildcard()},
176+
AllowList: database.AllowList{},
177177
IPAddress: pqtype.Inet{
178178
IPNet: net.IPNet{
179179
IP:net.ParseIP("0.0.0.0"),

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp