diff --git a/api/client/client.go b/api/client/client.go index 5f66d41aa0f7b..98355ca03bcd3 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -3706,51 +3706,39 @@ type ResourcePage[T types.ResourceWithLabels] struct { NextKey string } -// getResourceFromProtoPage extracts the resource from the PaginatedResource returned -// from the rpc ListUnifiedResources -func getResourceFromProtoPage(resource *proto.PaginatedResource) (types.ResourceWithLabels, error) { - var out types.ResourceWithLabels +// convertEnrichedResource extracts the resource and any enriched information from the +// PaginatedResource returned from the rpc ListUnifiedResources. +func convertEnrichedResource(resource *proto.PaginatedResource) (*types.EnrichedResource, error) { if r := resource.GetNode(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r, Logins: resource.Logins}, nil } else if r := resource.GetDatabaseServer(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetDatabaseService(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetAppServerOrSAMLIdPServiceProvider(); r != nil { //nolint:staticcheck // SA1019. TODO(sshah) DELETE IN 17.0 - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetWindowsDesktop(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetWindowsDesktopService(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetKubeCluster(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetKubernetesServer(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetUserGroup(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetAppServer(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else if r := resource.GetSAMLIdPServiceProvider(); r != nil { - out = r - return out, nil + return &types.EnrichedResource{ResourceWithLabels: r}, nil } else { return nil, trace.BadParameter("received unsupported resource %T", resource.Resource) } } -// ListUnifiedResourcePage is a helper for getting a single page of unified resources that match the provided request. -func ListUnifiedResourcePage(ctx context.Context, clt ListUnifiedResourcesClient, req *proto.ListUnifiedResourcesRequest) (ResourcePage[types.ResourceWithLabels], error) { - var out ResourcePage[types.ResourceWithLabels] +// GetUnifiedResourcePage is a helper for getting a single page of unified resources that match the provided request. +func GetUnifiedResourcePage(ctx context.Context, clt ListUnifiedResourcesClient, req *proto.ListUnifiedResourcesRequest) ([]*types.EnrichedResource, string, error) { + var out []*types.EnrichedResource // Set the limit to the default size if one was not provided within // an acceptable range. @@ -3766,26 +3754,24 @@ func ListUnifiedResourcePage(ctx context.Context, clt ListUnifiedResourcesClient req.Limit /= 2 // This is an extremely unlikely scenario, but better to cover it anyways. if req.Limit == 0 { - return out, trace.Wrap(err, "resource is too large to retrieve") + return nil, "", trace.Wrap(err, "resource is too large to retrieve") } continue } - return out, trace.Wrap(err) + return nil, "", trace.Wrap(err) } for _, respResource := range resp.Resources { - resource, err := getResourceFromProtoPage(respResource) + resource, err := convertEnrichedResource(respResource) if err != nil { - return out, trace.Wrap(err) + return nil, "", trace.Wrap(err) } - out.Resources = append(out.Resources, resource) + out = append(out, resource) } - out.NextKey = resp.NextKey - - return out, nil + return out, resp.NextKey, nil } } diff --git a/lib/client/api.go b/lib/client/api.go index fb121eb366757..5a3acf1c61070 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -2414,13 +2414,13 @@ func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Ser var servers []types.Server for { - page, err := client.ListUnifiedResourcePage(ctx, clt.AuthClient, &req) + page, next, err := client.GetUnifiedResourcePage(ctx, clt.AuthClient, &req) if err != nil { return nil, trace.Wrap(err) } - for _, r := range page.Resources { - srv, ok := r.(types.Server) + for _, r := range page { + srv, ok := r.ResourceWithLabels.(types.Server) if !ok { log.Warnf("expected types.Server but received unexpected type %T", r) continue @@ -2429,7 +2429,7 @@ func (tc *TeleportClient) ListNodesWithFilters(ctx context.Context) ([]types.Ser servers = append(servers, srv) } - req.StartKey = page.NextKey + req.StartKey = next if req.StartKey == "" { break } diff --git a/lib/web/apiserver.go b/lib/web/apiserver.go index 4130cfa231d3c..8483b4519c0a0 100644 --- a/lib/web/apiserver.go +++ b/lib/web/apiserver.go @@ -92,6 +92,7 @@ import ( "github.com/gravitational/teleport/lib/secret" "github.com/gravitational/teleport/lib/services" "github.com/gravitational/teleport/lib/session" + "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" "github.com/gravitational/teleport/lib/web/app" websession "github.com/gravitational/teleport/lib/web/session" @@ -2654,9 +2655,67 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq PredicateExpression: values.Get("query"), SearchKeywords: client.ParseSearchKeywords(values.Get("search"), ' '), UseSearchAsRoles: values.Get("searchAsRoles") == "yes", + IncludeLogins: true, }, nil } +type loginGetter interface { + GetAllowedLoginsForResource(resource services.AccessCheckable) ([]string, error) +} + +// calculateSSHLogins returns the subset of the allowedLogins that exist in +// the principals of the identity. This is required because SSH authorization +// only allows using a login that exists in the certificates valid principals. +// When connecting to servers in a leaf cluster, the root certificate is used, +// so we need to ensure that we only present the allowed logins that would +// result in a successful connection, if any exists. +func calculateSSHLogins(identity *tlsca.Identity, loginGetter loginGetter, r types.ResourceWithLabels, allowedLogins []string) ([]string, error) { + // TODO(tross) DELETE IN V17.0.0 + // This is here for backward compatibility in case the auth server + // does not support enriched resources yet. + if len(allowedLogins) == 0 { + logins, err := loginGetter.GetAllowedLoginsForResource(r) + if err != nil { + return nil, trace.Wrap(err) + } + slices.Sort(logins) + return logins, nil + } + + localLogins := identity.Principals + + allowed := make(map[string]struct{}) + for _, login := range allowedLogins { + allowed[login] = struct{}{} + } + + var logins []string + for _, local := range localLogins { + if _, ok := allowed[local]; ok { + logins = append(logins, local) + } + } + + slices.Sort(logins) + return logins, nil +} + +// calculateDesktopLogins determines the desktop logins allowed for the provided resource. +// If no logins are provided, then the checker is interrogated to determine which logins +// are allowed. +// +// TODO(tross) DELETE IN V17.0.0 +// This is here for backward compatibility if the auth server doesn't yet support enriching +// resources with login information. +func calculateDesktopLogins(loginGetter loginGetter, r types.ResourceWithLabels, allowedLogins []string) ([]string, error) { + if len(allowedLogins) > 0 { + return allowedLogins, nil + } + + logins, err := loginGetter.GetAllowedLoginsForResource(r) + return logins, trace.Wrap(err) +} + // clusterUnifiedResourcesGet returns a list of resources for a given cluster site. This includes all resources available to be displayed in the web ui // such as Nodes, Apps, Desktops, etc etc func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *http.Request, p httprouter.Params, sctx *SessionContext, site reversetunnelclient.RemoteSite) (interface{}, error) { @@ -2675,7 +2734,7 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt return nil, trace.Wrap(err) } - page, err := apiclient.ListUnifiedResourcePage(request.Context(), clt, req) + page, next, err := apiclient.GetUnifiedResourcePage(request.Context(), clt, req) if err != nil { return nil, trace.Wrap(err) } @@ -2718,15 +2777,16 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt var dbNames, dbUsers []string hasFetchedDBUsersAndNames := false - unifiedResources := make([]any, 0, len(page.Resources)) - for _, resource := range page.Resources { - switch r := resource.(type) { + unifiedResources := make([]any, 0, len(page)) + for _, enriched := range page { + switch r := enriched.ResourceWithLabels.(type) { case types.Server: - server, err := ui.MakeServer(site.GetName(), r, accessChecker) + logins, err := calculateSSHLogins(identity, accessChecker, r, enriched.Logins) if err != nil { return nil, trace.Wrap(err) } - unifiedResources = append(unifiedResources, server) + + unifiedResources = append(unifiedResources, ui.MakeServer(site.GetName(), r, logins)) case types.DatabaseServer: if !hasFetchedDBUsersAndNames { dbNames, dbUsers, err = getDatabaseUsersAndNames(accessChecker) @@ -2779,11 +2839,12 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt }) unifiedResources = append(unifiedResources, app) case types.WindowsDesktop: - desktop, err := ui.MakeDesktop(r, accessChecker) + logins, err := calculateDesktopLogins(accessChecker, r, enriched.Logins) if err != nil { return nil, trace.Wrap(err) } - unifiedResources = append(unifiedResources, desktop) + + unifiedResources = append(unifiedResources, ui.MakeDesktop(r, logins)) case types.KubeCluster: kube := ui.MakeKubeCluster(r, accessChecker) unifiedResources = append(unifiedResources, kube) @@ -2791,14 +2852,13 @@ func (h *Handler) clusterUnifiedResourcesGet(w http.ResponseWriter, request *htt kube := ui.MakeKubeCluster(r.GetCluster(), accessChecker) unifiedResources = append(unifiedResources, kube) default: - return nil, trace.Errorf("UI Resource has unknown type: %T", resource) + return nil, trace.Errorf("UI Resource has unknown type: %T", enriched) } } resp := listResourcesGetResponse{ - Items: unifiedResources, - StartKey: page.NextKey, - TotalCount: page.Total, + Items: unifiedResources, + StartKey: next, } return resp, nil @@ -2817,22 +2877,38 @@ func (h *Handler) clusterNodesGet(w http.ResponseWriter, r *http.Request, p http if err != nil { return nil, trace.Wrap(err) } + req.IncludeLogins = true - page, err := apiclient.GetResourcePage[types.Server](r.Context(), clt, req) + page, err := apiclient.GetEnrichedResourcePage(r.Context(), clt, req) if err != nil { return nil, trace.Wrap(err) } - accessChecker, err := sctx.GetUserAccessChecker() + identity, err := sctx.GetIdentity() if err != nil { return nil, trace.Wrap(err) } - uiServers, err := ui.MakeServers(site.GetName(), page.Resources, accessChecker) + accessChecker, err := sctx.GetUserAccessChecker() if err != nil { return nil, trace.Wrap(err) } + uiServers := make([]ui.Server, 0, len(page.Resources)) + for _, resource := range page.Resources { + server, ok := resource.ResourceWithLabels.(types.Server) + if !ok { + continue + } + + logins, err := calculateSSHLogins(identity, accessChecker, server, resource.Logins) + if err != nil { + return nil, trace.Wrap(err) + } + + uiServers = append(uiServers, ui.MakeServer(site.GetName(), server, logins)) + } + return listResourcesGetResponse{ Items: uiServers, StartKey: page.NextKey, diff --git a/lib/web/apiserver_test.go b/lib/web/apiserver_test.go index 4734796ed955b..32bc4de935333 100644 --- a/lib/web/apiserver_test.go +++ b/lib/web/apiserver_test.go @@ -9727,3 +9727,129 @@ func TestGithubConnector(t *testing.T) { assert.Empty(t, item) assert.Equal(t, http.StatusOK, resp.Code(), "unexpected status code getting connectors") } + +func TestCalculateSSHLogins(t *testing.T) { + cases := []struct { + name string + allowedLogins []string + grantedPrincipals []string + expectedLogins []string + loginGetter loginGetterFunc + }{ + { + name: "no matching logins", + allowedLogins: []string{"llama"}, + grantedPrincipals: []string{"fish"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return nil, nil + }, + }, + { + name: "no matching logins ignores fallback", + allowedLogins: []string{"llama"}, + grantedPrincipals: []string{"fish"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return []string{"apple", "banana"}, nil + }, + }, + { + name: "identical logins", + allowedLogins: []string{"llama", "shark", "goose"}, + grantedPrincipals: []string{"shark", "goose", "llama"}, + expectedLogins: []string{"goose", "shark", "llama"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return []string{"apple", "banana"}, nil + }, + }, + { + name: "subset of logins", + allowedLogins: []string{"llama"}, + grantedPrincipals: []string{"shark", "goose", "llama"}, + expectedLogins: []string{"llama"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return []string{"apple", "banana"}, nil + }, + }, + { + name: "no allowed logins", + grantedPrincipals: []string{"shark", "goose", "llama"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return nil, nil + }, + }, + { + name: "no allowed logins with fallback", + grantedPrincipals: []string{"shark", "goose", "llama"}, + expectedLogins: []string{"apple", "banana"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return []string{"apple", "banana"}, nil + }, + }, + { + name: "no granted logins", + allowedLogins: []string{"shark", "goose", "llama"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return nil, nil + }, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + identity := &tlsca.Identity{Principals: test.grantedPrincipals} + + logins, err := calculateSSHLogins(identity, test.loginGetter, nil, test.allowedLogins) + require.NoError(t, err) + require.Empty(t, cmp.Diff(logins, test.expectedLogins, cmpopts.SortSlices(func(a, b string) bool { + return strings.Compare(a, b) < 0 + }))) + }) + } +} + +func TestCalculateDesktopLogins(t *testing.T) { + cases := []struct { + name string + allowedLogins []string + expectedLogins []string + loginGetter loginGetterFunc + }{ + { + name: "allowed logins", + allowedLogins: []string{"llama", "fish", "dog"}, + expectedLogins: []string{"llama", "fish", "dog"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return nil, nil + }, + }, + { + name: "no allowed logins", + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return nil, nil + }, + }, + { + name: "no allowed logins with fallback", + expectedLogins: []string{"apple", "banana"}, + loginGetter: func(resource services.AccessCheckable) ([]string, error) { + return []string{"apple", "banana"}, nil + }, + }, + } + + for _, test := range cases { + t.Run(test.name, func(t *testing.T) { + logins, err := calculateDesktopLogins(test.loginGetter, nil, test.allowedLogins) + require.NoError(t, err) + require.Empty(t, cmp.Diff(logins, test.expectedLogins, cmpopts.SortSlices(func(a, b string) bool { + return strings.Compare(a, b) < 0 + }))) + }) + } +} + +type loginGetterFunc func(resource services.AccessCheckable) ([]string, error) + +func (f loginGetterFunc) GetAllowedLoginsForResource(resource services.AccessCheckable) ([]string, error) { + return f(resource) +} diff --git a/lib/web/integrations_awsoidc.go b/lib/web/integrations_awsoidc.go index a9ce2bea1e756..afbd97d352b87 100644 --- a/lib/web/integrations_awsoidc.go +++ b/lib/web/integrations_awsoidc.go @@ -475,6 +475,11 @@ func (h *Handler) awsOIDCListEC2(w http.ResponseWriter, r *http.Request, p httpr return nil, trace.Wrap(err) } + identity, err := sctx.GetIdentity() + if err != nil { + return nil, trace.Wrap(err) + } + accessChecker, err := sctx.GetUserAccessChecker() if err != nil { return nil, trace.Wrap(err) @@ -482,11 +487,12 @@ func (h *Handler) awsOIDCListEC2(w http.ResponseWriter, r *http.Request, p httpr servers := make([]ui.Server, 0, len(listResp.Servers)) for _, s := range listResp.Servers { - serverUI, err := ui.MakeServer(h.auth.clusterName, s, accessChecker) + logins, err := calculateSSHLogins(identity, accessChecker, s, nil) if err != nil { return nil, trace.Wrap(err) } - servers = append(servers, serverUI) + + servers = append(servers, ui.MakeServer(h.auth.clusterName, s, logins)) } return ui.AWSOIDCListEC2Response{ diff --git a/lib/web/servers.go b/lib/web/servers.go index 1bea139e44925..1543c5a96de34 100644 --- a/lib/web/servers.go +++ b/lib/web/servers.go @@ -187,7 +187,7 @@ func (h *Handler) clusterDesktopsGet(w http.ResponseWriter, r *http.Request, p h return nil, trace.Wrap(err) } - page, err := client.GetResourcePage[types.WindowsDesktop](r.Context(), clt, req) + page, err := client.GetEnrichedResourcePage(r.Context(), clt, req) if err != nil { return nil, trace.Wrap(err) } @@ -197,9 +197,19 @@ func (h *Handler) clusterDesktopsGet(w http.ResponseWriter, r *http.Request, p h return nil, trace.Wrap(err) } - uiDesktops, err := ui.MakeDesktops(page.Resources, accessChecker) - if err != nil { - return nil, trace.Wrap(err) + uiDesktops := make([]ui.Desktop, 0, len(page.Resources)) + for _, r := range page.Resources { + desktop, ok := r.ResourceWithLabels.(types.WindowsDesktop) + if !ok { + continue + } + + logins, err := calculateDesktopLogins(accessChecker, desktop, r.Logins) + if err != nil { + return nil, trace.Wrap(err) + } + + uiDesktops = append(uiDesktops, ui.MakeDesktop(desktop, logins)) } return listResourcesGetResponse{ @@ -244,8 +254,7 @@ func (h *Handler) getDesktopHandle(w http.ResponseWriter, r *http.Request, p htt desktopName := p.ByName("desktopName") - windowsDesktops, err := clt.GetWindowsDesktops(r.Context(), - types.WindowsDesktopFilter{Name: desktopName}) + windowsDesktops, err := clt.GetWindowsDesktops(r.Context(), types.WindowsDesktopFilter{Name: desktopName}) if err != nil { return nil, trace.Wrap(err) } @@ -261,12 +270,14 @@ func (h *Handler) getDesktopHandle(w http.ResponseWriter, r *http.Request, p htt // windowsDesktops may contain the same desktop multiple times // if multiple Windows Desktop Services are in use. We only need // to see the desktop once in the UI, so just take the first one. - uiDesktop, err := ui.MakeDesktop(windowsDesktops[0], accessChecker) + desktop := windowsDesktops[0] + + logins, err := accessChecker.GetAllowedLoginsForResource(desktop) if err != nil { return nil, trace.Wrap(err) } - return uiDesktop, nil + return ui.MakeDesktop(desktop, logins), nil } // desktopIsActive checks if a desktop has an active session and returns a desktopIsActive. @@ -429,10 +440,10 @@ func (h *Handler) handleNodeCreate(w http.ResponseWriter, r *http.Request, p htt return nil, trace.Wrap(err) } - uiServer, err := ui.MakeServer(site.GetName(), server, accessChecker) + logins, err := accessChecker.GetAllowedLoginsForResource(server) if err != nil { return nil, trace.Wrap(err) } - return uiServer, nil + return ui.MakeServer(site.GetName(), server, logins), nil } diff --git a/lib/web/ui/server.go b/lib/web/ui/server.go index 6f710ff3b7472..6037c1540e6ae 100644 --- a/lib/web/ui/server.go +++ b/lib/web/ui/server.go @@ -22,8 +22,6 @@ import ( "strconv" "strings" - "github.com/gravitational/trace" - "github.com/gravitational/teleport/api/constants" integrationv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" "github.com/gravitational/teleport/api/types" @@ -104,16 +102,11 @@ func (s sortedLabels) Swap(i, j int) { } // MakeServer creates a server object for the web ui -func MakeServer(clusterName string, server types.Server, accessChecker services.AccessChecker) (Server, error) { +func MakeServer(clusterName string, server types.Server, logins []string) Server { serverLabels := server.GetStaticLabels() serverCmdLabels := server.GetCmdLabels() uiLabels := makeLabels(serverLabels, transformCommandLabels(serverCmdLabels)) - serverLogins, err := accessChecker.GetAllowedLoginsForResource(server) - if err != nil { - return Server{}, trace.Wrap(err) - } - uiServer := Server{ Kind: server.GetKind(), ClusterName: clusterName, @@ -123,7 +116,7 @@ func MakeServer(clusterName string, server types.Server, accessChecker services. Addr: server.GetAddr(), Tunnel: server.GetUseTunnel(), SubKind: server.GetSubKind(), - SSHLogins: serverLogins, + SSHLogins: logins, } if server.GetSubKind() == types.SubKindOpenSSHEICENode { @@ -138,21 +131,7 @@ func MakeServer(clusterName string, server types.Server, accessChecker services. } } - return uiServer, nil -} - -// MakeServers creates server objects for webapp -func MakeServers(clusterName string, servers []types.Server, accessChecker services.AccessChecker) ([]Server, error) { - uiServers := []Server{} - for _, s := range servers { - server, err := MakeServer(clusterName, s, accessChecker) - if err != nil { - return nil, trace.Wrap(err, "making server for ui") - } - uiServers = append(uiServers, server) - } - - return uiServers, nil + return uiServer } // EKSCluster represents and EKS cluster, analog of awsoidc.EKSCluster, but used by web ui. @@ -455,7 +434,7 @@ type Desktop struct { } // MakeDesktop converts a desktop from its API form to a type the UI can display. -func MakeDesktop(windowsDesktop types.WindowsDesktop, accessChecker services.AccessChecker) (Desktop, error) { +func MakeDesktop(windowsDesktop types.WindowsDesktop, logins []string) Desktop { // stripRdpPort strips the default rdp port from an ip address since it is unimportant to display stripRdpPort := func(addr string) string { splitAddr := strings.Split(addr, ":") @@ -467,11 +446,6 @@ func MakeDesktop(windowsDesktop types.WindowsDesktop, accessChecker services.Acc uiLabels := makeLabels(windowsDesktop.GetAllLabels()) - logins, err := accessChecker.GetAllowedLoginsForResource(windowsDesktop) - if err != nil { - return Desktop{}, trace.Wrap(err) - } - return Desktop{ Kind: windowsDesktop.GetKind(), OS: constants.WindowsOS, @@ -480,22 +454,7 @@ func MakeDesktop(windowsDesktop types.WindowsDesktop, accessChecker services.Acc Labels: uiLabels, HostID: windowsDesktop.GetHostID(), Logins: logins, - }, nil -} - -// MakeDesktops converts desktops from their API form to a type the UI can display. -func MakeDesktops(windowsDesktops []types.WindowsDesktop, accessChecker services.AccessChecker) ([]Desktop, error) { - uiDesktops := make([]Desktop, 0, len(windowsDesktops)) - - for _, windowsDesktop := range windowsDesktops { - uiDesktop, err := MakeDesktop(windowsDesktop, accessChecker) - if err != nil { - return nil, trace.Wrap(err) - } - uiDesktops = append(uiDesktops, uiDesktop) } - - return uiDesktops, nil } // DesktopService describes a desktop service to pass to the ui. @@ -510,7 +469,7 @@ type DesktopService struct { Labels []Label `json:"labels"` } -// MakeDesktop converts a desktop from its API form to a type the UI can display. +// MakeDesktopService converts a desktop from its API form to a type the UI can display. func MakeDesktopService(desktopService types.WindowsDesktopService) DesktopService { uiLabels := makeLabels(desktopService.GetAllLabels()) diff --git a/lib/web/ui/server_test.go b/lib/web/ui/server_test.go index 5982bb8597953..97e47f3e668f7 100644 --- a/lib/web/ui/server_test.go +++ b/lib/web/ui/server_test.go @@ -21,6 +21,7 @@ package ui import ( "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" apidefaults "github.com/gravitational/teleport/api/defaults" @@ -379,7 +380,6 @@ func TestMakeServersHiddenLabels(t *testing.T) { clusterName string servers []types.Server expectedLabels [][]Label - roleSet services.RoleSet } testCases := []testCase{ @@ -405,11 +405,9 @@ func TestMakeServersHiddenLabels(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{}, "clustername", tc.roleSet) - servers, err := MakeServers(tc.clusterName, tc.servers, accessChecker) - require.NoError(t, err) - for i, server := range servers { - require.Equal(t, tc.expectedLabels[i], server.Labels) + for i, srv := range tc.servers { + server := MakeServer(tc.clusterName, srv, nil) + assert.Equal(t, tc.expectedLabels[i], server.Labels) } }) } @@ -453,9 +451,7 @@ func TestMakeDesktopHiddenLabel(t *testing.T) { ) require.NoError(t, err) - accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{}, "clustername", services.RoleSet{}) - desktop, err := MakeDesktop(windowsDesktop, accessChecker) - require.NoError(t, err) + desktop := MakeDesktop(windowsDesktop, nil) labels := []Label{ { Name: "label3", @@ -495,7 +491,6 @@ func TestSortedLabels(t *testing.T) { clusterName string servers []types.Server expectedLabels [][]Label - roleSet services.RoleSet } testCases := []testCase{ @@ -587,11 +582,9 @@ func TestSortedLabels(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - accessChecker := services.NewAccessCheckerWithRoleSet(&services.AccessInfo{}, "clustername", tc.roleSet) - servers, err := MakeServers(tc.clusterName, tc.servers, accessChecker) - require.NoError(t, err) - for i, server := range servers { - require.Equal(t, tc.expectedLabels[i], server.Labels) + for i, srv := range tc.servers { + server := MakeServer(tc.clusterName, srv, nil) + assert.Equal(t, tc.expectedLabels[i], server.Labels) } }) } diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 0470f29acce94..67107c39a40fc 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -1908,13 +1908,13 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *auth.Clien var collection serverCollection for { - page, err := apiclient.ListUnifiedResourcePage(ctx, client, &req) + page, next, err := apiclient.GetUnifiedResourcePage(ctx, client, &req) if err != nil { return nil, trace.Wrap(err) } - for _, r := range page.Resources { - srv, ok := r.(types.Server) + for _, r := range page { + srv, ok := r.ResourceWithLabels.(types.Server) if !ok { log.Warnf("expected types.Server but received unexpected type %T", r) continue @@ -1931,7 +1931,7 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *auth.Clien } } - req.StartKey = page.NextKey + req.StartKey = next if req.StartKey == "" { break }