Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2777,6 +2777,8 @@ func (c *Client) ListResources(ctx context.Context, req proto.ListResourcesReque
resources[i] = respResource.GetKubeCluster()
case types.KindKubeServer:
resources[i] = respResource.GetKubernetesServer()
case types.KindUserGroup:
resources[i] = respResource.GetUserGroup()
default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
}
Expand Down Expand Up @@ -2867,6 +2869,8 @@ func GetResourcePage[T types.ResourceWithLabels](ctx context.Context, clt GetRes
resource = respResource.GetKubeCluster()
case types.KindKubeServer:
resource = respResource.GetKubernetesServer()
case types.KindUserGroup:
resource = respResource.GetUserGroup()
default:
out.Resources = nil
return out, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
Expand Down
1,732 changes: 907 additions & 825 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,8 @@ message PaginatedResource {
types.WindowsDesktopServiceV3 WindowsDesktopService = 8 [(gogoproto.jsontag) = "windows_desktop_service,omitempty"];
// DatabaseService represents a DatabaseService resource.
types.DatabaseServiceV1 DatabaseService = 9 [(gogoproto.jsontag) = "database_service,omitempty"];
// UserGroup represents a UserGroup resource.
types.UserGroupV1 UserGroup = 10 [(gogoproto.jsontag) = "user_group,omitempty"];
}
}

Expand Down
13 changes: 13 additions & 0 deletions api/types/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,19 @@ func (r ResourcesWithLabels) AsKubeServers() ([]KubeServer, error) {
return servers, nil
}

// AsUserGroups converts each resource into type UserGroup.
func (r ResourcesWithLabels) AsUserGroups() ([]UserGroup, error) {
userGroups := make([]UserGroup, 0, len(r))
for _, resource := range r {
userGroup, ok := resource.(UserGroup)
if !ok {
return nil, trace.BadParameter("expected types.UserGroup, got: %T", resource)
}
userGroups = append(userGroups, userGroup)
}
return userGroups, nil
}

// GetVersion returns resource version
func (h *ResourceHeader) GetVersion() string {
return h.Version
Expand Down
1 change: 1 addition & 0 deletions api/types/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@ func (r *RoleV6) CheckAndSetDefaults() error {
r.Spec.Allow.KubernetesLabels,
r.Spec.Allow.DatabaseLabels,
r.Spec.Allow.WindowsDesktopLabels,
r.Spec.Allow.GroupLabels,
} {
if err := checkWildcardSelector(labels); err != nil {
return trace.Wrap(err)
Expand Down
25 changes: 25 additions & 0 deletions api/types/usergroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package types

import (
"fmt"
"sort"

"github.com/gravitational/trace"

Expand Down Expand Up @@ -85,6 +86,30 @@ func (g UserGroups) AsResources() []ResourceWithLabels {
return resources
}

// SortByCustom custom sorts by given sort criteria.
func (g UserGroups) SortByCustom(sortBy SortBy) error {
if sortBy.Field == "" {
return nil
}

isDesc := sortBy.IsDesc
switch sortBy.Field {
case ResourceMetadataName:
sort.SliceStable(g, func(i, j int) bool {
return stringCompare(g[i].GetName(), g[j].GetName(), isDesc)
})
case ResourceSpecDescription:
sort.SliceStable(g, func(i, j int) bool {
return stringCompare(g[i].GetMetadata().Description, g[j].GetMetadata().Description, isDesc)
})

default:
return trace.NotImplemented("sorting by field %q for resource %q is not supported", sortBy.Field, KindKubeServer)
}

return nil
}

// Len returns the slice length.
func (g UserGroups) Len() int { return len(g) }

Expand Down
126 changes: 116 additions & 10 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1615,7 +1615,7 @@ func (a *ServerWithRoles) ListResources(ctx context.Context, req proto.ListResou
// https://github.com/gravitational/teleport/pull/1224
actionVerbs = []string{types.VerbList}

case types.KindDatabaseServer, types.KindDatabaseService, types.KindAppServer, types.KindKubeServer, types.KindWindowsDesktop, types.KindWindowsDesktopService:
case types.KindDatabaseServer, types.KindDatabaseService, types.KindAppServer, types.KindKubeServer, types.KindWindowsDesktop, types.KindWindowsDesktopService, types.KindUserGroup:

default:
return nil, trace.NotImplemented("resource type %s does not support pagination", req.ResourceType)
Expand Down Expand Up @@ -1713,15 +1713,23 @@ func (r resourceChecker) CanAccess(resource types.Resource) error {
return r.CheckAccess(rr, state)
case types.WindowsDesktopService:
return r.CheckAccess(rr, state)
default:
return trace.BadParameter("could not check access to resource type %T", r)
case types.UserGroup:
// Because usergroup only has ResourceWithLabels, it looks like this will match
// everything. To get around this, we'll match on it last and then double check
// that the kind is equal to usergroup. If it's not, we'll fall through and return
// the bad parameter as expected.
if rr.GetKind() == types.KindUserGroup {
return r.CheckAccess(rr, state)
}
}

return trace.BadParameter("could not check access to resource type %T", r)
}

// newResourceAccessChecker creates a resourceAccessChecker for the provided resource type
func (a *ServerWithRoles) newResourceAccessChecker(resource string) (resourceAccessChecker, error) {
switch resource {
case types.KindAppServer, types.KindDatabaseServer, types.KindDatabaseService, types.KindWindowsDesktop, types.KindWindowsDesktopService, types.KindNode, types.KindKubeServer:
case types.KindAppServer, types.KindDatabaseServer, types.KindDatabaseService, types.KindWindowsDesktop, types.KindWindowsDesktopService, types.KindNode, types.KindKubeServer, types.KindUserGroup:
return &resourceChecker{AccessChecker: a.context.Checker}, nil
default:
return nil, trace.BadParameter("could not check access to resource type %s", resource)
Expand Down Expand Up @@ -1812,6 +1820,29 @@ func (a *ServerWithRoles) listResourcesWithSort(ctx context.Context, req proto.L
return nil, trace.Wrap(err)
}
resources = desktops.AsResources()
case types.KindUserGroup:
var allUserGroups types.UserGroups
userGroups, nextKey, err := a.ListUserGroups(ctx, int(req.Limit), "")
for {
if err != nil {
return nil, trace.Wrap(err)
}

for _, ug := range userGroups {
allUserGroups = append(allUserGroups, ug)
}

if nextKey == "" {
break
}

userGroups, nextKey, err = a.ListUserGroups(ctx, int(req.Limit), nextKey)
}

if err := allUserGroups.SortByCustom(req.SortBy); err != nil {
return nil, trace.Wrap(err)
}
resources = allUserGroups.AsResources()

default:
return nil, trace.NotImplemented("resource type %q is not supported for listResourcesWithSort", req.ResourceType)
Expand Down Expand Up @@ -5829,13 +5860,57 @@ func (a *ServerWithRoles) DeleteAllSAMLIdPServiceProviders(ctx context.Context)
return trace.Wrap(err)
}

func (a *ServerWithRoles) checkAccessToUserGroup(userGroup types.UserGroup) error {
return a.context.Checker.CheckAccess(
userGroup,
// MFA is not required for operations on user group resources.
services.AccessState{MFAVerified: true})
}

// ListUserGroups returns a paginated list of user group resources.
func (a *ServerWithRoles) ListUserGroups(ctx context.Context, pageSize int, nextToken string) ([]types.UserGroup, string, error) {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbList); err != nil {
return nil, "", trace.Wrap(err)
}

return a.authServer.ListUserGroups(ctx, pageSize, nextToken)
// We have to set a default here.
if pageSize == 0 {
pageSize = local.GroupMaxPageSize
}

// Because access to user groups is determined by label, we'll need to calculate the entire list of
// user groups and then check access to those user groups.
var filteredUserGroups []types.UserGroup

// Use the default page size since we're assembling our pages manually here.
userGroups, nextToken, err := a.authServer.ListUserGroups(ctx, 0, nextToken)
for {
if err != nil {
return nil, "", trace.Wrap(err)
}

for _, userGroup := range userGroups {
err := a.checkAccessToUserGroup(userGroup)
if err != nil && !trace.IsAccessDenied(err) {
return nil, "", trace.Wrap(err)
} else if err == nil {
filteredUserGroups = append(filteredUserGroups, userGroup)
}
}

if nextToken == "" {
break
}

userGroups, nextToken, err = a.authServer.ListUserGroups(ctx, 0, nextToken)
}

numUserGroups := len(filteredUserGroups)
if numUserGroups <= pageSize {
return filteredUserGroups, "", nil
}

return filteredUserGroups[:pageSize], backend.NextPaginationKey(filteredUserGroups[pageSize-1]), nil
}

// GetUserGroup returns the specified user group resources.
Expand All @@ -5844,25 +5919,47 @@ func (a *ServerWithRoles) GetUserGroup(ctx context.Context, name string) (types.
return nil, trace.Wrap(err)
}

return a.authServer.GetUserGroup(ctx, name)
userGroup, err := a.authServer.GetUserGroup(ctx, name)
if err != nil {
return nil, trace.Wrap(err)
}

if err := a.checkAccessToUserGroup(userGroup); err != nil {
return nil, trace.Wrap(err)
}

return userGroup, nil
}

// CreateUserGroup creates a new user group resource.
func (a *ServerWithRoles) CreateUserGroup(ctx context.Context, g types.UserGroup) error {
func (a *ServerWithRoles) CreateUserGroup(ctx context.Context, userGroup types.UserGroup) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbCreate); err != nil {
return trace.Wrap(err)
}

return a.authServer.CreateUserGroup(ctx, g)
if err := a.checkAccessToUserGroup(userGroup); err != nil {
return trace.Wrap(err)
}

return a.authServer.CreateUserGroup(ctx, userGroup)
}

// UpdateUserGroup updates an existing user group resource.
func (a *ServerWithRoles) UpdateUserGroup(ctx context.Context, g types.UserGroup) error {
func (a *ServerWithRoles) UpdateUserGroup(ctx context.Context, userGroup types.UserGroup) error {
if err := a.action(apidefaults.Namespace, types.KindUserGroup, types.VerbUpdate); err != nil {
return trace.Wrap(err)
}

return a.authServer.UpdateUserGroup(ctx, g)
previousUserGroup, err := a.authServer.GetUserGroup(ctx, userGroup.GetName())
if err != nil {
return trace.Wrap(err)
}

if err := a.checkAccessToUserGroup(previousUserGroup); err != nil {
return trace.Wrap(err)
}

return a.authServer.UpdateUserGroup(ctx, userGroup)
}

// DeleteUserGroup removes the specified user group resource.
Expand All @@ -5871,6 +5968,15 @@ func (a *ServerWithRoles) DeleteUserGroup(ctx context.Context, name string) erro
return trace.Wrap(err)
}

previousUserGroup, err := a.authServer.GetUserGroup(ctx, name)
if err != nil {
return trace.Wrap(err)
}

if err := a.checkAccessToUserGroup(previousUserGroup); err != nil {
return trace.Wrap(err)
}

return a.authServer.DeleteUserGroup(ctx, name)
}

Expand Down
Loading