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
178 changes: 44 additions & 134 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1541,109 +1541,31 @@ func (a *ServerWithRoles) GetNode(ctx context.Context, namespace, name string) (
return node, nil
}

func (s *ServerWithRoles) MakePaginatedResources(requestType string, resources []types.ResourceWithLabels) ([]*proto.PaginatedResource, error) {
paginatedResources := make([]*proto.PaginatedResource, 0, len(resources))
for _, resource := range resources {
var protoResource *proto.PaginatedResource
resourceKind := requestType
if requestType == types.KindUnifiedResource {
resourceKind = resource.GetKind()
}
switch resourceKind {
case types.KindDatabaseServer:
database, ok := resource.(*types.DatabaseServerV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_DatabaseServer{DatabaseServer: database}}
case types.KindDatabaseService:
databaseService, ok := resource.(*types.DatabaseServiceV1)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_DatabaseService{DatabaseService: databaseService}}
case types.KindAppServer:
app, ok := resource.(*types.AppServerV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_AppServer{AppServer: app}}
case types.KindNode:
srv, ok := resource.(*types.ServerV2)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_Node{Node: srv}}
case types.KindKubeServer:
srv, ok := resource.(*types.KubernetesServerV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubernetesServer{KubernetesServer: srv}}
case types.KindWindowsDesktop:
desktop, ok := resource.(*types.WindowsDesktopV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}
func (a *ServerWithRoles) checkUnifiedAccess(resource types.ResourceWithLabels, checker resourceAccessChecker, filter services.MatchResourceFilter, resourceAccessMap map[string]error) (bool, error) {
resourceKind := resource.GetKind()

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktop{WindowsDesktop: desktop}}
case types.KindWindowsDesktopService:
desktopService, ok := resource.(*types.WindowsDesktopServiceV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}
if canAccessErr := resourceAccessMap[resourceKind]; canAccessErr != nil {
// skip access denied error. It is expected that resources won't be available
// to some users and we want to keep iterating until we've reached the request limit
// of resources they have access to
if trace.IsAccessDenied(canAccessErr) {
return false, nil
}
return false, trace.Wrap(canAccessErr)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_WindowsDesktopService{WindowsDesktopService: desktopService}}
case types.KindKubernetesCluster:
cluster, ok := resource.(*types.KubernetesClusterV3)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}
if resourceKind != types.KindSAMLIdPServiceProvider {
if err := checker.CanAccess(resource); err != nil {

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_KubeCluster{KubeCluster: cluster}}
case types.KindUserGroup:
userGroup, ok := resource.(*types.UserGroupV1)
if !ok {
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
}

protoResource = &proto.PaginatedResource{Resource: &proto.PaginatedResource_UserGroup{UserGroup: userGroup}}
case types.KindSAMLIdPServiceProvider, types.KindAppOrSAMLIdPServiceProvider:
switch appOrSP := resource.(type) {
case *types.AppServerV3:
protoResource = &proto.PaginatedResource{
Resource: &proto.PaginatedResource_AppServerOrSAMLIdPServiceProvider{
AppServerOrSAMLIdPServiceProvider: &types.AppServerOrSAMLIdPServiceProviderV1{
Resource: &types.AppServerOrSAMLIdPServiceProviderV1_AppServer{
AppServer: appOrSP,
},
},
}}
case *types.SAMLIdPServiceProviderV1:
protoResource = &proto.PaginatedResource{
Resource: &proto.PaginatedResource_AppServerOrSAMLIdPServiceProvider{
AppServerOrSAMLIdPServiceProvider: &types.AppServerOrSAMLIdPServiceProviderV1{
Resource: &types.AppServerOrSAMLIdPServiceProviderV1_SAMLIdPServiceProvider{
SAMLIdPServiceProvider: appOrSP,
},
},
}}
default:
return nil, trace.BadParameter("%s has invalid type %T", resourceKind, resource)
if trace.IsAccessDenied(err) {
return false, nil
}

default:
return nil, trace.NotImplemented("resource type %s doesn't support pagination", resource.GetKind())
return false, trace.Wrap(err)
}

paginatedResources = append(paginatedResources, protoResource)
}
return paginatedResources, nil

match, err := services.MatchResourceByFilters(resource, filter, nil)
return match, trace.Wrap(err)
}

// ListUnifiedResources returns a paginated list of unified resources filtered by user access.
Expand All @@ -1661,6 +1583,26 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
Kinds: req.Kinds,
}

// resourceAccessMap is a map of resourceKind to error, that holds any errors returned when checking verbs
// for that resource (list/read)
resourceAccessMap := make(map[string]error)

// we want to populate the resourceAccessMap at the start of the request for all available kind in the
// unified resource cache
for _, kind := range services.UnifiedResourceKinds {
actionVerbs := []string{types.VerbList, types.VerbRead}
if kind == types.KindNode {
// We are checking list only for Nodes to keep backwards compatibility.
// The read verb got added to GetNodes initially in:
// https://github.com/gravitational/teleport/pull/1209
// but got removed shortly afterwards in:
// https://github.com/gravitational/teleport/pull/1224
actionVerbs = []string{types.VerbList}
}

resourceAccessMap[kind] = a.withOptions(quietAction(true)).action(apidefaults.Namespace, kind, actionVerbs...)
}

// Apply any requested additional search_as_roles and/or preview_as_roles
// for the duration of the search.
if req.UseSearchAsRoles || req.UsePreviewAsRoles {
Expand All @@ -1683,10 +1625,11 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
}()
}

resourceChecker, err := a.newResourceAccessChecker(types.KindUnifiedResource)
checker, err := a.newResourceAccessChecker(types.KindUnifiedResource)
if err != nil {
return nil, trace.Wrap(err)
}

if req.PinnedOnly {
prefs, err := a.authServer.GetUserPreferences(ctx, a.context.User.GetName())
if err != nil {
Expand All @@ -1696,24 +1639,8 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
return &proto.ListUnifiedResourcesResponse{}, nil
}
unifiedResources, err = a.authServer.UnifiedResourceCache.GetUnifiedResourcesByIDs(ctx, prefs.ClusterPreferences.PinnedResources.GetResourceIds(), func(resource types.ResourceWithLabels) (bool, error) {
var err error
switch r := resource.(type) {
// TODO (avatus) we should add this type into the `resourceChecker.CanAccess` method
case types.SAMLIdPServiceProvider:
err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbList)
default:
err = resourceChecker.CanAccess(r)
}
if err != nil {
// skip access denied error. It is expected that resources won't be available
// to some users and we want to keep iterating until we've reached the request limit
// of resources they have access to
if trace.IsAccessDenied(err) {
return false, nil
}
return false, trace.Wrap(err)
}
return services.MatchResourceByFilters(resource, filter, nil)
match, err := a.checkUnifiedAccess(resource, checker, filter, resourceAccessMap)
return match, trace.Wrap(err)
})
if err != nil {
return nil, trace.Wrap(err)
Expand All @@ -1727,24 +1654,7 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
}
} else {
unifiedResources, nextKey, err = a.authServer.UnifiedResourceCache.IterateUnifiedResources(ctx, func(resource types.ResourceWithLabels) (bool, error) {
var err error
switch r := resource.(type) {
case types.SAMLIdPServiceProvider:
err = a.action(apidefaults.Namespace, types.KindSAMLIdPServiceProvider, types.VerbList)
default:
err = resourceChecker.CanAccess(r)
}

if err != nil {
// skip access denied error. It is expected that resources won't be available
// to some users and we want to keep iterating until we've reached the request limit
// of resources they have access to
if trace.IsAccessDenied(err) {
return false, nil
}
return false, trace.Wrap(err)
}
match, err := services.MatchResourceByFilters(resource, filter, nil)
match, err := a.checkUnifiedAccess(resource, checker, filter, resourceAccessMap)
return match, trace.Wrap(err)
}, req)
if err != nil {
Expand Down
24 changes: 19 additions & 5 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4470,7 +4470,7 @@ func TestListUnifiedResources_MixedAccess(t *testing.T) {
require.NoError(t, err)

// add desktops
desktop, err := types.NewWindowsDesktopV3(name, map[string]string{"name": "mylabel"},
desktop, err := types.NewWindowsDesktopV3(name, nil,
types.WindowsDesktopSpecV3{Addr: "_", HostID: "_"})
require.NoError(t, err)
require.NoError(t, srv.Auth().UpsertWindowsDesktop(ctx, desktop))
Expand All @@ -4490,24 +4490,38 @@ func TestListUnifiedResources_MixedAccess(t *testing.T) {
// create user, role, and client
username := "user"
user, role, err := CreateUserAndRole(srv.Auth(), username, nil, nil)
// remove permission from nodes and desktops
require.NoError(t, err)

role.SetNodeLabels(types.Allow, types.Labels{"*": {"*"}})
role.SetDatabaseLabels(types.Allow, types.Labels{"*": {"*"}})
role.SetWindowsDesktopLabels(types.Allow, types.Labels{"*": {"*"}})
err = srv.Auth().UpsertRole(ctx, role)
require.NoError(t, err)
// remove permission from nodes by labels
role.SetNodeLabels(types.Deny, types.Labels{"name": {"mylabel"}})
require.NoError(t, srv.Auth().UpsertRole(ctx, role))
// remove permission from desktops by rule
denyRules := []types.Rule{{
Resources: []string{types.KindWindowsDesktop},
Verbs: []string{types.VerbList, types.VerbRead},
}}
role.SetRules(types.Deny, denyRules)
err = srv.Auth().UpsertRole(ctx, role)
require.NoError(t, err)
// require.NoError(t, err)
identity := TestUser(user.GetName())
clt, err := srv.NewClient(identity)
require.NoError(t, err)

require.NoError(t, err)
resp, err := clt.ListUnifiedResources(ctx, &proto.ListUnifiedResourcesRequest{
Limit: 10,
Limit: 20,
SortBy: types.SortBy{IsDesc: true, Field: types.ResourceMetadataName},
})
require.NoError(t, err)
require.Len(t, resp.Resources, 6)
require.Empty(t, resp.NextKey)

// only receive databases
// only receive databases because nodes are denied with labels and desktops are denied with a verb rule
for _, resource := range resp.Resources {
r := resource.GetDatabaseServer()
require.Equal(t, types.KindDatabaseServer, r.GetKind())
Expand Down
2 changes: 1 addition & 1 deletion lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4393,7 +4393,7 @@ func (g *GRPCServer) ListResources(ctx context.Context, req *authpb.ListResource
return nil, trace.Wrap(err)
}

paginatedResources, err := auth.MakePaginatedResources(req.ResourceType, resp.Resources)
paginatedResources, err := services.MakePaginatedResources(req.ResourceType, resp.Resources)
if err != nil {
return nil, trace.Wrap(err, "making paginated resources")
}
Expand Down
3 changes: 3 additions & 0 deletions lib/services/unified_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ import (
"github.com/gravitational/teleport/lib/utils"
)

// UnifiedResourceKinds is a list of all kinds that are stored in the unified resource cache.
var UnifiedResourceKinds []string = []string{types.KindNode, types.KindKubeServer, types.KindDatabaseServer, types.KindAppServer, types.KindSAMLIdPServiceProvider, types.KindWindowsDesktop}

// UnifiedResourceCacheConfig is used to configure a UnifiedResourceCache
type UnifiedResourceCacheConfig struct {
// BTreeDegree is a degree of B-Tree, 2 for example, will create a
Expand Down
2 changes: 1 addition & 1 deletion web/packages/design/src/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const StyledCheckbox = styled.input.attrs({ type: 'checkbox' })`
}

&:hover {
cursor: pointer;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
}

&::before {
Expand Down
16 changes: 8 additions & 8 deletions web/packages/design/src/Menu/MenuItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ const fromTheme = props => {
fontWeight: values.theme.regular,

'&:hover, &:focus': {
color: values.theme.colors.text.main,
color: props.disabled
? values.theme.colors.text.disabled
: values.theme.colors.text.main,
background: values.theme.colors.spotBackground[0],
},
'&:active': {
Expand All @@ -47,20 +49,18 @@ const fromTheme = props => {
const MenuItem = styled.div`
min-height: 40px;
box-sizing: border-box;
cursor: pointer;
cursor: ${props => (props.disabled ? 'not-allowed' : 'pointer')};
display: flex;
justify-content: flex-start;
align-items: center;
min-width: 140px;
overflow: hidden;
text-decoration: none;
white-space: nowrap;
color: ${props => props.theme.colors.text.main};

&:hover,
&:focus {
text-decoration: none;
}
color: ${props =>
props.disabled
? props.theme.colors.text.disabled
: props.theme.colors.text.main};

${fromTheme}
`;
Expand Down
Loading