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

Commit4c9762e

Browse files
committed
feat: introduce typed allow_list for RBAC scopes
The allow_list for RBAC scopes has been updated to use typed elementsof the form `{type: string, id: string}` instead of raw string IDs.This change enables more granular authorization policies. Specifically, itmodifies the behavior for "create" actions. A create operation is nowpermitted if the scope's allow_list contains an entry matching theresource type, even without a specific ID. This is useful for scenarioslike workspace agent tokens which need to create resources but cannotknow the ID ahead of time.For all other actions (e.g., read, update, delete), the allow_listmust still contain an entry that matches both the type and the specificID of the resource.The Rego policy, relevant Go code, and tests have been updated toimplement and verify this new typed allow_list behavior.
1 parent154d4a1 commit4c9762e

File tree

5 files changed

+136
-11
lines changed

5 files changed

+136
-11
lines changed

‎coderd/database/modelmethods_internal_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func TestAPIKeyScopesExpand(t *testing.T) {
9393
expanded,err:=effective.Expand()
9494
require.NoError(t,err)
9595
require.Len(t,expanded.AllowIDList,1)
96-
require.Equal(t,"workspace",expanded.AllowIDList[0].Type)
96+
require.Equal(t,rbac.ResourceWorkspace.Type,expanded.AllowIDList[0].Type)
9797
require.Equal(t,workspaceID.String(),expanded.AllowIDList[0].ID)
9898
})
9999

‎coderd/database/querier_test.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4928,7 +4928,7 @@ func createPrebuiltWorkspace(
49284928
dbgen.WorkspaceBuild(t,db, database.WorkspaceBuild{
49294929
CreatedAt:createdAt,
49304930
WorkspaceID:workspace.ID,
4931-
TemplateVersionID:extTmplVersion.ID,
4931+
TemplateVersionID:extTmplVersion.TemplateVersion.ID,
49324932
BuildNumber:1,
49334933
Transition:database.WorkspaceTransitionStart,
49344934
InitiatorID:tmpl.CreatedBy,

‎coderd/rbac/authz_internal_test.go‎

Lines changed: 90 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,8 @@ func TestAuthorizeScope(t *testing.T) {
10001000
},
10011001
)
10021002

1003+
createScopeWorkspaceID:=uuid.New()
1004+
10031005
// This scope can only create workspaces
10041006
user=Subject{
10051007
ID:"me",
@@ -1012,15 +1014,15 @@ func TestAuthorizeScope(t *testing.T) {
10121014
Identifier:RoleIdentifier{Name:"create_workspace"},
10131015
DisplayName:"Create Workspace",
10141016
Site:Permissions(map[string][]policy.Action{
1015-
// Onlyread access for workspaces.
1017+
// Onlycreate access for workspaces.
10161018
ResourceWorkspace.Type: {policy.ActionCreate},
10171019
}),
10181020
Org:map[string][]Permission{},
10191021
User: []Permission{},
10201022
},
1021-
//Empty string allow_list is allowed for actions like 'create'
1023+
//Specific IDs still allow creation; reads require matching IDs.
10221024
AllowIDList: []AllowListElement{{
1023-
Type:ResourceWorkspace.Type,ID:"",
1025+
Type:ResourceWorkspace.Type,ID:createScopeWorkspaceID.String(),
10241026
}},
10251027
},
10261028
}
@@ -1190,8 +1192,7 @@ func TestScopeAllowList(t *testing.T) {
11901192
Site:allPermsExcept(ResourceUser),
11911193
},
11921194
AllowIDList: []AllowListElement{
1193-
{Type:ResourceWorkspace.Type,ID:wid.String()},
1194-
{Type:ResourceWorkspace.Type,ID:""},// Allow to create
1195+
{Type:ResourceWorkspace.Type,ID:wid.String()},// create bypass handled by policy
11951196
{Type:ResourceTemplate.Type,ID:policy.WildcardSymbol},
11961197
{Type:ResourceGroup.Type,ID:gid.String()},
11971198

@@ -1219,6 +1220,7 @@ func TestScopeAllowList(t *testing.T) {
12191220

12201221
// Group
12211222
{resource:ResourceGroup.InOrg(defOrg).WithID(gid),actions: []policy.Action{policy.ActionRead}},
1223+
{resource:ResourceGroup.InOrg(defOrg),actions: []policy.Action{policy.ActionCreate}},
12221224
},
12231225
),
12241226

@@ -1233,13 +1235,40 @@ func TestScopeAllowList(t *testing.T) {
12331235

12341236
// `wid` matches on the uuid, but not the type
12351237
{resource:ResourceGroup.WithID(wid),actions: []policy.Action{policy.ActionRead}},
1236-
1237-
// no empty id for the create action
1238-
{resource:ResourceGroup.InOrg(defOrg),actions: []policy.Action{policy.ActionCreate}},
12391238
},
12401239
),
12411240
)
12421241

1242+
t.Run("create requires matching type entry",func(t*testing.T) {
1243+
t.Parallel()
1244+
1245+
subject:=Subject{
1246+
ID:"me",
1247+
Roles:Roles{must(RoleByName(RoleOwner()))},
1248+
Scope:Scope{
1249+
Role:Role{
1250+
Identifier:RoleIdentifier{Name:"AllowListNoWorkspace"},
1251+
Site:Permissions(map[string][]policy.Action{
1252+
ResourceWorkspace.Type: {policy.ActionCreate},
1253+
}),
1254+
},
1255+
AllowIDList: []AllowListElement{{
1256+
Type:ResourceTemplate.Type,ID:uuid.NewString(),
1257+
}},
1258+
},
1259+
}
1260+
1261+
testAuthorize(t,"CreateRequiresMatchingType",subject,
1262+
[]authTestCase{
1263+
{
1264+
resource:ResourceWorkspace.InOrg(defOrg).WithOwner(subject.ID),
1265+
actions: []policy.Action{policy.ActionCreate},
1266+
allow:false,
1267+
},
1268+
},
1269+
)
1270+
})
1271+
12431272
// Wildcard type
12441273
user=Subject{
12451274
ID:"me",
@@ -1271,6 +1300,7 @@ func TestScopeAllowList(t *testing.T) {
12711300
[]authTestCase{
12721301
// anything with the id is ok
12731302
{resource:ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID).WithID(wid),actions: []policy.Action{policy.ActionRead}},
1303+
{resource:ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID),actions: []policy.Action{policy.ActionCreate}},
12741304
{resource:ResourceGroup.InOrg(defOrg).WithID(wid),actions: []policy.Action{policy.ActionRead}},
12751305
{resource:ResourceTemplate.InOrg(defOrg).WithID(wid),actions: []policy.Action{policy.ActionRead}},
12761306
},
@@ -1283,13 +1313,64 @@ func TestScopeAllowList(t *testing.T) {
12831313
},
12841314
[]authTestCase{
12851315
// Anything without the id is not allowed
1286-
{resource:ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID),actions: []policy.Action{policy.ActionCreate}},
12871316
{resource:ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID).WithID(uuid.New()),actions: []policy.Action{policy.ActionRead}},
12881317
},
12891318
),
12901319
)
12911320
}
12921321

1322+
funcTestScopeAllowListFilter(t*testing.T) {
1323+
t.Parallel()
1324+
1325+
workspaceID:=uuid.New()
1326+
otherWorkspace:=uuid.New()
1327+
defOrg:=uuid.New()
1328+
1329+
subject:=Subject{
1330+
ID:"me",
1331+
Roles:Roles{must(RoleByName(RoleOwner()))},
1332+
Scope:Scope{
1333+
Role:Role{
1334+
Identifier:RoleIdentifier{Name:"AllowListFilter"},
1335+
Site:Permissions(map[string][]policy.Action{
1336+
ResourceWorkspace.Type: {policy.ActionRead},
1337+
}),
1338+
},
1339+
AllowIDList: []AllowListElement{
1340+
{Type:ResourceWorkspace.Type,ID:workspaceID.String()},
1341+
},
1342+
},
1343+
}
1344+
1345+
allowed:=ResourceWorkspace.WithID(workspaceID).InOrg(defOrg).WithOwner(subject.ID)
1346+
denied:=ResourceWorkspace.WithID(otherWorkspace).InOrg(defOrg).WithOwner(subject.ID)
1347+
1348+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
1349+
defercancel()
1350+
1351+
auth:=NewAuthorizer(prometheus.NewRegistry())
1352+
filtered,err:=Filter(ctx,auth,subject,policy.ActionRead, []Object{allowed,denied})
1353+
require.NoError(t,err)
1354+
require.Len(t,filtered,1)
1355+
require.Equal(t,workspaceID.String(),filtered[0].ID)
1356+
}
1357+
1358+
funcTestAuthorizeRequiresScope(t*testing.T) {
1359+
t.Parallel()
1360+
1361+
subject:=Subject{
1362+
ID:"me",
1363+
Roles:Roles{must(RoleByName(RoleOwner()))},
1364+
}
1365+
1366+
auth:=NewAuthorizer(prometheus.NewRegistry())
1367+
ctx,cancel:=context.WithTimeout(context.Background(),testutil.WaitShort)
1368+
defercancel()
1369+
1370+
err:=auth.Authorize(ctx,subject,policy.ActionRead,ResourceWorkspace.WithID(uuid.New()))
1371+
require.Error(t,err)
1372+
}
1373+
12931374
funcTestScopeMetricsCounters(t*testing.T) {
12941375
t.Parallel()
12951376

‎coderd/rbac/policy.rego‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,9 +257,29 @@ scope_allow_list if {
257257
input.subject.scope.allow_list[_]== {"type": input.object.type,"id":"*"}
258258
}
259259

260+
# Allow create operations when the allow_list grants the resource type (or global wildcard)
261+
# even if the object ID is not yet known.
262+
scope_allow_list if{
263+
input.action=="create"
264+
not{"type":"*","id":"*"} ininput.subject.scope.allow_list
265+
not{"type": input.object.type,"id":"*"} ininput.subject.scope.allow_list
266+
267+
input.object.id==""
268+
269+
allowed_ids:= {allowed_id|
270+
# Iterate over all allow list elements matching the object type
271+
ele:= input.subject.scope.allow_list[_]
272+
ele.type in[input.object.type,"*"]
273+
allowed_id:= ele.id
274+
}
275+
276+
allowed_ids[_]
277+
}
278+
260279
# A comprehension that iterates over the allow_list and checks if the
261280
# (object.type, object.id) is in the allowed ids.
262281
scope_allow_list if{
282+
input.action!="create"
263283
# If the wildcard is listed in the allow_list, we do not care about the
264284
# object.id. This line is included to prevent partial compilations from
265285
# ever needing to include the object.id.

‎coderd/rbac/scopes_test.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package rbac_test
33
import (
44
"testing"
55

6+
"github.com/google/uuid"
67
"github.com/stretchr/testify/require"
78

89
"github.com/coder/coder/v2/coderd/rbac"
@@ -61,3 +62,26 @@ func TestExpandScope(t *testing.T) {
6162
}
6263
})
6364
}
65+
66+
funcTestWorkspaceAgentScopeAllowList(t*testing.T) {
67+
t.Parallel()
68+
69+
workspaceID:=uuid.New()
70+
ownerID:=uuid.New()
71+
templateID:=uuid.New()
72+
versionID:=uuid.New()
73+
74+
scope:=rbac.WorkspaceAgentScope(rbac.WorkspaceAgentScopeParams{
75+
WorkspaceID:workspaceID,
76+
OwnerID:ownerID,
77+
TemplateID:templateID,
78+
VersionID:versionID,
79+
})
80+
81+
require.ElementsMatch(t, []rbac.AllowListElement{
82+
{Type:rbac.ResourceWorkspace.Type,ID:workspaceID.String()},
83+
{Type:rbac.ResourceTemplate.Type,ID:templateID.String()},
84+
{Type:rbac.ResourceTemplate.Type,ID:versionID.String()},
85+
{Type:rbac.ResourceUser.Type,ID:ownerID.String()},
86+
},scope.AllowIDList)
87+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp