mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add API key scope to restrict access to user data (#17692)
This commit is contained in:
@@ -1053,6 +1053,64 @@ func TestAuthorizeScope(t *testing.T) {
|
||||
{resource: ResourceWorkspace.InOrg(unusedID).WithOwner("not-me"), actions: []policy.Action{policy.ActionCreate}, allow: false},
|
||||
},
|
||||
)
|
||||
|
||||
meID := uuid.New()
|
||||
user = Subject{
|
||||
ID: meID.String(),
|
||||
Roles: Roles{
|
||||
must(RoleByName(RoleMember())),
|
||||
must(RoleByName(ScopedRoleOrgMember(defOrg))),
|
||||
},
|
||||
Scope: must(ScopeNoUserData.Expand()),
|
||||
}
|
||||
|
||||
// Test 1: Verify that no_user_data scope prevents accessing user data
|
||||
testAuthorize(t, "ReadPersonalUser", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = ResourceUser.AvailableActions()
|
||||
c.allow = false
|
||||
c.resource.ID = meID.String()
|
||||
return c
|
||||
}, []authTestCase{
|
||||
{resource: ResourceUser.WithOwner(meID.String()).InOrg(defOrg).WithID(meID)},
|
||||
}),
|
||||
)
|
||||
|
||||
// Test 2: Verify token can still perform regular member actions that don't involve user data
|
||||
testAuthorize(t, "NoUserData_CanStillUseRegularPermissions", user,
|
||||
// Test workspace access - should still work
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []policy.Action{policy.ActionRead}
|
||||
c.allow = true
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Can still read owned workspaces
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
}),
|
||||
// Test workspace create - should still work
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []policy.Action{policy.ActionCreate}
|
||||
c.allow = true
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Can still create workspaces
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner(user.ID)},
|
||||
}),
|
||||
)
|
||||
|
||||
// Test 3: Verify token cannot perform actions outside of member role
|
||||
testAuthorize(t, "NoUserData_CannotExceedMemberRole", user,
|
||||
cases(func(c authTestCase) authTestCase {
|
||||
c.actions = []policy.Action{policy.ActionRead, policy.ActionUpdate, policy.ActionDelete}
|
||||
c.allow = false
|
||||
return c
|
||||
}, []authTestCase{
|
||||
// Cannot access other users' workspaces
|
||||
{resource: ResourceWorkspace.InOrg(defOrg).WithOwner("other-user")},
|
||||
// Cannot access admin resources
|
||||
{resource: ResourceOrganization.WithID(defOrg)},
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
// cases applies a given function to all test cases. This makes generalities easier to create.
|
||||
|
||||
+30
-8
@@ -11,10 +11,11 @@ import (
|
||||
)
|
||||
|
||||
type WorkspaceAgentScopeParams struct {
|
||||
WorkspaceID uuid.UUID
|
||||
OwnerID uuid.UUID
|
||||
TemplateID uuid.UUID
|
||||
VersionID uuid.UUID
|
||||
WorkspaceID uuid.UUID
|
||||
OwnerID uuid.UUID
|
||||
TemplateID uuid.UUID
|
||||
VersionID uuid.UUID
|
||||
BlockUserData bool
|
||||
}
|
||||
|
||||
// WorkspaceAgentScope returns a scope that is the same as ScopeAll but can only
|
||||
@@ -25,16 +26,25 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope {
|
||||
panic("all uuids must be non-nil, this is a developer error")
|
||||
}
|
||||
|
||||
allScope, err := ScopeAll.Expand()
|
||||
if err != nil {
|
||||
panic("failed to expand scope all, this should never happen")
|
||||
var (
|
||||
scope Scope
|
||||
err error
|
||||
)
|
||||
if params.BlockUserData {
|
||||
scope, err = ScopeNoUserData.Expand()
|
||||
} else {
|
||||
scope, err = ScopeAll.Expand()
|
||||
}
|
||||
if err != nil {
|
||||
panic("failed to expand scope, this should never happen")
|
||||
}
|
||||
|
||||
return Scope{
|
||||
// TODO: We want to limit the role too to be extra safe.
|
||||
// Even though the allowlist blocks anything else, it is still good
|
||||
// incase we change the behavior of the allowlist. The allowlist is new
|
||||
// and evolving.
|
||||
Role: allScope.Role,
|
||||
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.
|
||||
@@ -50,6 +60,7 @@ func WorkspaceAgentScope(params WorkspaceAgentScopeParams) Scope {
|
||||
const (
|
||||
ScopeAll ScopeName = "all"
|
||||
ScopeApplicationConnect ScopeName = "application_connect"
|
||||
ScopeNoUserData ScopeName = "no_user_data"
|
||||
)
|
||||
|
||||
// TODO: Support passing in scopeID list for allowlisting resources.
|
||||
@@ -81,6 +92,17 @@ var builtinScopes = map[ScopeName]Scope{
|
||||
},
|
||||
AllowIDList: []string{policy.WildcardSymbol},
|
||||
},
|
||||
|
||||
ScopeNoUserData: {
|
||||
Role: Role{
|
||||
Identifier: RoleIdentifier{Name: fmt.Sprintf("Scope_%s", ScopeNoUserData)},
|
||||
DisplayName: "Scope without access to user data",
|
||||
Site: allPermsExcept(ResourceUser),
|
||||
Org: map[string][]Permission{},
|
||||
User: []Permission{},
|
||||
},
|
||||
AllowIDList: []string{policy.WildcardSymbol},
|
||||
},
|
||||
}
|
||||
|
||||
type ExpandableScope interface {
|
||||
|
||||
Reference in New Issue
Block a user