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
1,715 changes: 880 additions & 835 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions api/proto/teleport/legacy/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1862,6 +1862,9 @@ message ListUnifiedResourcesRequest {
// UsePreviewAsRoles indicates that the response should include all resources
// the caller would be able to access with their preview_as_roles
bool UsePreviewAsRoles = 10 [(gogoproto.jsontag) = "use_preview_as_roles,omitempty"];
// PinnedOnly indicates that the request will pull only the pinned resources
// of the requesting user
bool PinnedOnly = 11 [(gogoproto.jsontag) = "pinned_only,omitempty"];
}

// ListUnifiedResourceResponse response of ListUnifiedResources.
Expand Down
3 changes: 3 additions & 0 deletions api/types/appserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,9 @@ func (s *AppServerV3) SetRotation(r Rotation) {

// GetApp returns the app this app server proxies.
func (s *AppServerV3) GetApp() Application {
if s.Spec.App == nil {
return nil
}
return s.Spec.App
}

Expand Down
3 changes: 3 additions & 0 deletions api/types/databaseserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,9 @@ func (s *DatabaseServerV3) SetRotation(r Rotation) {

// GetDatabase returns the database this database server proxies.
func (s *DatabaseServerV3) GetDatabase() Database {
if s.Spec.Database == nil {
return nil
}
return s.Spec.Database
}

Expand Down
3 changes: 3 additions & 0 deletions api/types/kubernetes_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,9 @@ func (s *KubernetesServerV3) SetRotation(r Rotation) {

// GetCluster returns the cluster this kube server proxies.
func (s *KubernetesServerV3) GetCluster() KubeCluster {
if s.Spec.Cluster == nil {
return nil
}
return s.Spec.Cluster
}

Expand Down
47 changes: 33 additions & 14 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,7 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
elapsedFilter time.Duration
unifiedResources types.ResourcesWithLabels
filteredResources types.ResourcesWithLabels
nextKey string
)

defer func() {
Expand All @@ -1521,8 +1522,6 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
len(unifiedResources), len(filteredResources), elapsedFetch+elapsedFilter)
}()

startFetch := time.Now()
startFilter := time.Now()
filter := services.MatchResourceFilter{
Labels: req.Labels,
SearchKeywords: req.SearchKeywords,
Expand All @@ -1534,21 +1533,41 @@ func (a *ServerWithRoles) ListUnifiedResources(ctx context.Context, req *proto.L
if err != nil {
return nil, trace.Wrap(err)
}

unifiedResources, nextKey, err := a.authServer.UnifiedResourceCache.IterateUnifiedResources(ctx, func(resource types.ResourceWithLabels) (bool, error) {
if err := resourceChecker.CanAccess(resource); err != nil {
if trace.IsAccessDenied(err) {
return false, nil
startFetch := time.Now()
startFilter := time.Now()
if req.PinnedOnly {
prefs, err := a.authServer.GetUserPreferences(ctx, a.context.User.GetName())
if err != nil {
return nil, trace.Wrap(err, "getting user preferences")
}
if len(prefs.ClusterPreferences.PinnedResources.ResourceIds) == 0 {
return &proto.ListUnifiedResourcesResponse{}, nil
}
unifiedResources, err = a.authServer.UnifiedResourceCache.GetUnifiedResourcesByIDs(ctx, prefs.ClusterPreferences.PinnedResources.GetResourceIds(), func(resource types.ResourceWithLabels) bool {
if err := resourceChecker.CanAccess(resource); err != nil {
return false
}
return false, trace.Wrap(err)
match, _ := services.MatchResourceByFilters(resource, filter, nil)
return match
})
if err != nil {
return nil, trace.Wrap(err, "getting unified resources by ID")
}
} else {
unifiedResources, nextKey, err = a.authServer.UnifiedResourceCache.IterateUnifiedResources(ctx, func(resource types.ResourceWithLabels) (bool, error) {
if err := resourceChecker.CanAccess(resource); err != nil {
if trace.IsAccessDenied(err) {
return false, nil
}
return false, trace.Wrap(err)
}
match, err := services.MatchResourceByFilters(resource, filter, nil)
return match, trace.Wrap(err)
}, req)
if err != nil {
return nil, trace.Wrap(err, "filtering unified resources")
}
match, err := services.MatchResourceByFilters(resource, filter, nil)
return match, trace.Wrap(err)
}, req)
if err != nil {
return nil, trace.Wrap(err, "filtering unified resources")
}

elapsedFetch = time.Since(startFetch)
elapsedFilter = time.Since(startFilter)

Expand Down
57 changes: 57 additions & 0 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (
"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/constants"
apidefaults "github.com/gravitational/teleport/api/defaults"
userpreferencesv1 "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
"github.com/gravitational/teleport/api/types"
apievents "github.com/gravitational/teleport/api/types/events"
"github.com/gravitational/teleport/api/types/installers"
Expand Down Expand Up @@ -4210,6 +4211,62 @@ func TestListUnifiedResources_KindsFilter(t *testing.T) {
}
}

func TestListUnifiedResources_WithPinnedResources(t *testing.T) {
t.Parallel()
ctx := context.Background()
srv := newTestTLSServer(t)
names := []string{"tifa", "cloud", "aerith", "baret", "cid", "tifa2"}
for _, name := range names {

// add nodes
node, err := types.NewServerWithLabels(
name,
types.KindNode,
types.ServerSpecV2{
Hostname: name,
},
map[string]string{"name": name},
)
require.NoError(t, err)

_, err = srv.Auth().UpsertNode(ctx, node)
require.NoError(t, err)
}

// create user, role, and client
username := "theuser"
user, _, err := CreateUserAndRole(srv.Auth(), username, nil, nil)
require.NoError(t, err)
identity := TestUser(user.GetName())

// pin a resource
pinned := &userpreferencesv1.PinnedResourcesUserPreferences{
ResourceIds: []string{"tifa/tifa/node"},
}
clusterPrefs := &userpreferencesv1.ClusterUserPreferences{
PinnedResources: pinned,
}

req := &userpreferencesv1.UpsertUserPreferencesRequest{
Preferences: &userpreferencesv1.UserPreferences{
ClusterPreferences: clusterPrefs,
},
}
err = srv.Auth().UpsertUserPreferences(ctx, username, req.Preferences)
require.NoError(t, err)

clt, err := srv.NewClient(identity)
require.NoError(t, err)
resp, err := clt.ListUnifiedResources(ctx, &proto.ListUnifiedResourcesRequest{
PinnedOnly: true,
})
require.NoError(t, err)
require.Len(t, resp.Resources, 1)
require.Empty(t, resp.NextKey)
// Check that our returned resource is the pinned resource
require.Equal(t, "tifa", resp.Resources[0].GetNode().GetHostname())
}

// TestListUnifiedResources_WithSearch will generate multiple resources
// and filter by a search query
func TestListUnifiedResources_WithSearch(t *testing.T) {
Expand Down
25 changes: 25 additions & 0 deletions lib/services/unified_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,31 @@ func (c *UnifiedResourceCache) GetUnifiedResources(ctx context.Context) ([]types
return resources, nil
}

// GetUnifiedResourcesByIDs will take a list of ids and return any items found in the unifiedResourceCache tree by id and that return true from matchFn
func (c *UnifiedResourceCache) GetUnifiedResourcesByIDs(ctx context.Context, ids []string, matchFn func(types.ResourceWithLabels) bool) ([]types.ResourceWithLabels, error) {
var resources []types.ResourceWithLabels

err := c.read(ctx, func(cache *UnifiedResourceCache) error {
for _, id := range ids {
key := backend.Key(prefix, id)
res, found := cache.nameTree.Get(&item{Key: key})
if !found || res == nil {
continue
}
resource := cache.resources[res.Value]
if matched := matchFn(resource); matched {
resources = append(resources, resource.CloneResource())
}
}
return nil
})
if err != nil {
return nil, trace.Wrap(err, "getting unified resources by id")
}

return resources, nil
}

// ResourceGetter is an interface that provides a way to fetch all the resources
// that can be stored in the UnifiedResourceCache
type ResourceGetter interface {
Expand Down
1 change: 1 addition & 0 deletions lib/web/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -2524,6 +2524,7 @@ func makeUnifiedResourceRequest(r *http.Request) (*proto.ListUnifiedResourcesReq
Limit: limit,
StartKey: startKey,
SortBy: sortBy,
PinnedOnly: values.Get("pinnedOnly") == "true",
PredicateExpression: values.Get("query"),
SearchKeywords: client.ParseSearchKeywords(values.Get("search"), ' '),
UseSearchAsRoles: values.Get("searchAsRoles") == "yes",
Expand Down
13 changes: 10 additions & 3 deletions lib/web/userpreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,14 +159,21 @@ func (h *Handler) updateUserPreferences(_ http.ResponseWriter, r *http.Request,
// userPreferencesResponse creates a JSON response for the user preferences.
func userPreferencesResponse(resp *userpreferencesv1.UserPreferences) *UserPreferencesResponse {
jsonResp := &UserPreferencesResponse{
Assist: assistUserPreferencesResponse(resp.Assist),
Theme: resp.Theme,
Onboard: onboardUserPreferencesResponse(resp.Onboard),
Assist: assistUserPreferencesResponse(resp.Assist),
Theme: resp.Theme,
Onboard: onboardUserPreferencesResponse(resp.Onboard),
ClusterPreferences: clusterPreferencesResponse(resp.ClusterPreferences),
}

return jsonResp
}

func clusterPreferencesResponse(resp *userpreferencesv1.ClusterUserPreferences) ClusterUserPreferencesResponse {
return ClusterUserPreferencesResponse{
PinnedResources: resp.PinnedResources.ResourceIds,
}
}

// assistUserPreferencesResponse creates a JSON response for the assist user preferences.
func assistUserPreferencesResponse(resp *userpreferencesv1.AssistUserPreferences) AssistUserPreferencesResponse {
jsonResp := AssistUserPreferencesResponse{
Expand Down