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

feat: scope allow_list to includeresource_type#19748

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Emyrk merged 6 commits intomainfromstevenmasley/better_allow_list
Sep 17, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletioncoderd/rbac/README.md
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -169,7 +169,7 @@ This command answers the question: “Is the user allowed?”
### Partial Evaluation

```bash
opa eval --partial --format=pretty 'data.authz.allow' -d policy.rego --unknowns input.object.owner --unknowns input.object.org_owner --unknowns input.object.acl_user_list --unknowns input.object.acl_group_list -i input.json
opa eval --partial --format=pretty 'data.authz.allow' -d policy.rego --unknowns input.object.id --unknowns input.object.owner --unknowns input.object.org_owner --unknowns input.object.acl_user_list --unknowns input.object.acl_group_list -i input.json
```

This command performs a partial evaluation of the policy, specifying a set of unknown input parameters.
Expand Down
18 changes: 17 additions & 1 deletioncoderd/rbac/astvalue.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -182,9 +182,25 @@ func (s Scope) regoValue() ast.Value {
if !ok {
panic("developer error: role is not an object")
}

terms := make([]*ast.Term, len(s.AllowIDList))
for i, v := range s.AllowIDList {
terms[i] = ast.NewTerm(ast.NewObject(
[2]*ast.Term{
ast.StringTerm("type"),
ast.StringTerm(v.Type),
},
[2]*ast.Term{
ast.StringTerm("id"),
ast.StringTerm(v.ID),
},
),
)
}

r.Insert(
ast.StringTerm("allow_list"),
ast.NewTerm(regoSliceString(s.AllowIDList...)),
ast.NewTerm(ast.NewArray(terms...)),
)
return r
}
Expand Down
133 changes: 130 additions & 3 deletionscoderd/rbac/authz_internal_test.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -929,7 +929,7 @@ func TestAuthorizeScope(t *testing.T) {
Org: map[string][]Permission{},
User: []Permission{},
},
AllowIDList: []string{workspaceID.String()},
AllowIDList: []AllowListElement{{Type: ResourceWorkspace.Type, ID:workspaceID.String()}},
},
}

Expand DownExpand Up@@ -1019,7 +1019,9 @@ func TestAuthorizeScope(t *testing.T) {
User: []Permission{},
},
// Empty string allow_list is allowed for actions like 'create'
AllowIDList: []string{""},
AllowIDList: []AllowListElement{{
Type: ResourceWorkspace.Type, ID: "",
}},
},
}

Expand DownExpand Up@@ -1145,7 +1147,7 @@ func TestAuthorizeScope(t *testing.T) {
ResourceUser.Type: {policy.ActionRead},
}),
},
AllowIDList: []string{policy.WildcardSymbol},
AllowIDList: []AllowListElement{AllowListAll()},
},
}

Expand All@@ -1163,6 +1165,131 @@ func TestAuthorizeScope(t *testing.T) {
)
}

func TestScopeAllowList(t *testing.T) {
t.Parallel()

defOrg := uuid.New()

// Some IDs to use
wid := uuid.New()
gid := uuid.New()

user := Subject{
ID: "me",
Roles: Roles{
must(RoleByName(RoleOwner())),
},
Scope: Scope{
Role: Role{
Identifier: RoleIdentifier{
Name: "AllowList",
OrganizationID: defOrg,
},
DisplayName: "AllowList",
// Allow almost everything
Site: allPermsExcept(ResourceUser),
},
AllowIDList: []AllowListElement{
{Type: ResourceWorkspace.Type, ID: wid.String()},
{Type: ResourceWorkspace.Type, ID: ""}, // Allow to create
{Type: ResourceTemplate.Type, ID: policy.WildcardSymbol},
{Type: ResourceGroup.Type, ID: gid.String()},

// This scope allows all users, but the permissions do not.
{Type: ResourceUser.Type, ID: policy.WildcardSymbol},
},
},
}

testAuthorize(t, "AllowList", user,
// Allowed:
cases(func(c authTestCase) authTestCase {
c.allow = true
return c
},
[]authTestCase{
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID).WithID(wid), actions: []policy.Action{policy.ActionRead}},
// matching on empty id
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionCreate}},

// Template has wildcard ID, so any uuid is allowed, including the empty
{resource: ResourceTemplate.InOrg(defOrg).WithID(uuid.New()), actions: AllActions()},
{resource: ResourceTemplate.InOrg(defOrg).WithID(uuid.New()), actions: AllActions()},
{resource: ResourceTemplate.InOrg(defOrg), actions: AllActions()},

// Group
{resource: ResourceGroup.InOrg(defOrg).WithID(gid), actions: []policy.Action{policy.ActionRead}},
},
),

// Not allowed:
cases(func(c authTestCase) authTestCase {
c.allow = false
return c
},
[]authTestCase{
// Has the scope and allow list, but not the permission
{resource: ResourceUser.WithOwner(user.ID), actions: []policy.Action{policy.ActionRead}},

// `wid` matches on the uuid, but not the type
{resource: ResourceGroup.WithID(wid), actions: []policy.Action{policy.ActionRead}},

// no empty id for the create action
{resource: ResourceGroup.InOrg(defOrg), actions: []policy.Action{policy.ActionCreate}},
},
),
)

// Wildcard type
user = Subject{
ID: "me",
Roles: Roles{
must(RoleByName(RoleOwner())),
},
Scope: Scope{
Role: Role{
Identifier: RoleIdentifier{
Name: "WildcardType",
OrganizationID: defOrg,
},
DisplayName: "WildcardType",
// Allow almost everything
Site: allPermsExcept(ResourceUser),
},
AllowIDList: []AllowListElement{
{Type: policy.WildcardSymbol, ID: wid.String()},
},
},
}

testAuthorize(t, "WildcardType", user,
// Allowed:
cases(func(c authTestCase) authTestCase {
c.allow = true
return c
},
[]authTestCase{
// anything with the id is ok
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID).WithID(wid), actions: []policy.Action{policy.ActionRead}},
{resource: ResourceGroup.InOrg(defOrg).WithID(wid), actions: []policy.Action{policy.ActionRead}},
{resource: ResourceTemplate.InOrg(defOrg).WithID(wid), actions: []policy.Action{policy.ActionRead}},
},
),

// Not allowed:
cases(func(c authTestCase) authTestCase {
c.allow = false
return c
},
[]authTestCase{
// Anything without the id is not allowed
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID), actions: []policy.Action{policy.ActionCreate}},
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID).WithID(uuid.New()), actions: []policy.Action{policy.ActionRead}},
},
),
)
}

// cases applies a given function to all test cases. This makes generalities easier to create.
func cases(opt func(c authTestCase) authTestCase, cases []authTestCase) []authTestCase {
if opt == nil {
Expand Down
8 changes: 6 additions & 2 deletionscoderd/rbac/input.json
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
{
"action": "never-match-action",
"action": "read",
"object": {
"id": "9046b041-58ed-47a3-9c3a-de302577875a",
"owner": "00000000-0000-0000-0000-000000000000",
Expand DownExpand Up@@ -40,7 +40,11 @@
],
"org": {},
"user": [],
"allow_list": ["*"]
"allow_list": [
{
"type": "workspace",
"id": "*"
}]
}
}
}
31 changes: 26 additions & 5 deletionscoderd/rbac/policy.rego
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -246,18 +246,39 @@ user_allow(roles) := num if {
num := number(allow)
}

# Scope allow_list is a list of resourceIDs explicitly allowed by the scope.
# If the listis '*', then all resources are allowed.
# Scope allow_list is a list of resource(Type, ID) tuples explicitly allowed by the scope.
# If the listcontains `(*,*)`, then all resources are allowed.
scope_allow_list if {
"*" ininput.subject.scope.allow_list
input.subject.scope.allow_list[_] == {"type": "*", "id": "*"}
}

# This is a shortcut if the allow_list contains (type, *), then allow all IDs of that type.
scope_allow_list if {
input.subject.scope.allow_list[_] == {"type": input.object.type, "id": "*"}
}

# A comprehension that iterates over the allow_list and checks if the
# (object.type, object.id) is in the allowed ids.
scope_allow_list if {
# If the wildcard is listed in the allow_list, we do not care about the
# object.id. This line is included to prevent partial compilations from
# ever needing to include the object.id.
not "*" in input.subject.scope.allow_list
input.object.id in input.subject.scope.allow_list
not {"type": "*", "id": "*"} in input.subject.scope.allow_list
# This is equivalent to the above line, as `type` is known at partial query time.
not {"type": input.object.type, "id": "*"} in input.subject.scope.allow_list

# allows_ids is the set of all ids allowed for the given object.type
allowed_ids := {allowed_id |
# Iterate over all allow list elements
ele := input.subject.scope.allow_list[_]
ele.type in [input.object.type, "*"]
allowed_id := ele.id
}

# Return if the object.id is in the allowed ids
# This rule is evaluated at the end so the partial query can use the object.id
# against this precomputed set of allowed ids.
input.object.id in allowed_ids
}

# -------------------
Expand Down
35 changes: 23 additions & 12 deletionscoderd/rbac/scopes.go
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -45,14 +45,15 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope {
// incase we change the behavior of the allowlist. The allowlist is new
// and evolving.
Role: scope.Role,
// This prevents the agent from being able to access any other resource.
// Include the list of IDs of anything that is required for the
// agent to function.
AllowIDList: []string{
params.WorkspaceID.String(),
params.TemplateID.String(),
params.VersionID.String(),
params.OwnerID.String(),

// Limit the agent to only be able to access the singular workspace and
// the template/version it was created from. Add additional resources here
// as needed, but do not add more workspace or template resource ids.
AllowIDList: []AllowListElement{
{Type: ResourceWorkspace.Type, ID: params.WorkspaceID.String()},
{Type: ResourceTemplate.Type, ID: params.TemplateID.String()},
{Type: ResourceTemplate.Type, ID: params.VersionID.String()},
{Type: ResourceUser.Type, ID: params.OwnerID.String()},
},
}
}
Expand All@@ -77,7 +78,7 @@ var builtinScopes = map[ScopeName]Scope{
Org: map[string][]Permission{},
User: []Permission{},
},
AllowIDList: []string{policy.WildcardSymbol},
AllowIDList: []AllowListElement{AllowListAll()},
},

ScopeApplicationConnect: {
Expand All@@ -90,7 +91,7 @@ var builtinScopes = map[ScopeName]Scope{
Org: map[string][]Permission{},
User: []Permission{},
},
AllowIDList: []string{policy.WildcardSymbol},
AllowIDList: []AllowListElement{AllowListAll()},
},

ScopeNoUserData: {
Expand All@@ -101,7 +102,7 @@ var builtinScopes = map[ScopeName]Scope{
Org: map[string][]Permission{},
User: []Permission{},
},
AllowIDList: []string{policy.WildcardSymbol},
AllowIDList: []AllowListElement{AllowListAll()},
},
}

Expand DownExpand Up@@ -129,7 +130,17 @@ func (name ScopeName) Name() RoleIdentifier {
// AllowIDList. Eg: 'AllowIDList: []string{WildcardSymbol}'
type Scope struct {
Role
AllowIDList []string `json:"allow_list"`
AllowIDList []AllowListElement `json:"allow_list"`
}

type AllowListElement struct {
// ID must be a string to allow for the wildcard symbol.
ID string `json:"id"`
Type string `json:"type"`
}

func AllowListAll() AllowListElement {
return AllowListElement{ID: policy.WildcardSymbol, Type: policy.WildcardSymbol}
}

func (s Scope) Expand() (Scope, error) {
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp