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
130 changes: 78 additions & 52 deletions lib/teleterm/clusters/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import (
"github.com/gravitational/trace"
"github.com/jonboulle/clockwork"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"

"github.com/gravitational/teleport/api/client/proto"
"github.com/gravitational/teleport/api/client/webclient"
"github.com/gravitational/teleport/api/types"
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
"github.com/gravitational/teleport/lib/auth"
Expand Down Expand Up @@ -85,73 +87,97 @@ func (c *Cluster) Connected() bool {
// and enabled enterprise features. This method requires a valid cert.
func (c *Cluster) GetWithDetails(ctx context.Context, authClient auth.ClientI) (*ClusterWithDetails, error) {
var (
authPingResponse proto.PingResponse
caps *types.AccessCapabilities
authClusterID string
acl *api.ACL
user types.User
clusterPingResponse *webclient.PingResponse
authPingResponse proto.PingResponse
caps *types.AccessCapabilities
authClusterID string
acl *api.ACL
user types.User
roles []types.Role
)

clusterPingResponse, err := c.clusterClient.Ping(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
group, groupCtx := errgroup.WithContext(ctx)

//TODO(gzdunek): These calls should be done in parallel.
err = AddMetadataToRetryableError(ctx, func() error {
authPingResponse, err = authClient.Ping(ctx)
if err != nil {
return trace.Wrap(err)
}
group.Go(func() error {
res, err := c.clusterClient.Ping(groupCtx)
clusterPingResponse = res
return trace.Wrap(err)
})

caps, err = authClient.GetAccessCapabilities(ctx, types.AccessCapabilitiesRequest{
RequestableRoles: true,
SuggestedReviewers: true,
})
if err != nil {
group.Go(func() error {
err := AddMetadataToRetryableError(groupCtx, func() error {
res, err := authClient.Ping(groupCtx)
authPingResponse = res
return trace.Wrap(err)
}
})
return trace.Wrap(err)
})

clusterName, err := authClient.GetClusterName()
if err != nil {
group.Go(func() error {
err := AddMetadataToRetryableError(groupCtx, func() error {
res, err := authClient.GetAccessCapabilities(groupCtx, types.AccessCapabilitiesRequest{
RequestableRoles: true,
SuggestedReviewers: true,
})
caps = res
return trace.Wrap(err)
}
authClusterID = clusterName.GetClusterID()
})
return trace.Wrap(err)
})

user, err = authClient.GetCurrentUser(ctx)
if err != nil {
return trace.Wrap(err)
}
group.Go(func() error {
err := AddMetadataToRetryableError(groupCtx, func() error {
clusterName, err := authClient.GetClusterName()
if err != nil {
return trace.Wrap(err)
}
authClusterID = clusterName.GetClusterID()
return nil
})
return trace.Wrap(err)
})

roles, err := authClient.GetCurrentUserRoles(ctx)
if err != nil {
group.Go(func() error {
err := AddMetadataToRetryableError(groupCtx, func() error {
res, err := authClient.GetCurrentUser(groupCtx)
user = res
return trace.Wrap(err)
}
})
return trace.Wrap(err)
})

roleSet := services.NewRoleSet(roles...)
userACL := services.NewUserACL(user, roleSet, *authPingResponse.ServerFeatures, false, false)

acl = &api.ACL{
RecordedSessions: convertToAPIResourceAccess(userACL.RecordedSessions),
ActiveSessions: convertToAPIResourceAccess(userACL.ActiveSessions),
AuthConnectors: convertToAPIResourceAccess(userACL.AuthConnectors),
Roles: convertToAPIResourceAccess(userACL.Roles),
Users: convertToAPIResourceAccess(userACL.Users),
TrustedClusters: convertToAPIResourceAccess(userACL.TrustedClusters),
Events: convertToAPIResourceAccess(userACL.Events),
Tokens: convertToAPIResourceAccess(userACL.Tokens),
Servers: convertToAPIResourceAccess(userACL.Nodes),
Apps: convertToAPIResourceAccess(userACL.AppServers),
Dbs: convertToAPIResourceAccess(userACL.DBServers),
Kubeservers: convertToAPIResourceAccess(userACL.KubeServers),
AccessRequests: convertToAPIResourceAccess(userACL.AccessRequests),
}
return nil
group.Go(func() error {
err := AddMetadataToRetryableError(groupCtx, func() error {
res, err := authClient.GetCurrentUserRoles(groupCtx)
roles = res
return trace.Wrap(err)
})
return trace.Wrap(err)
})
if err != nil {

if err := group.Wait(); err != nil {
return nil, trace.Wrap(err)
}

roleSet := services.NewRoleSet(roles...)
userACL := services.NewUserACL(user, roleSet, *authPingResponse.ServerFeatures, false, false)

acl = &api.ACL{
RecordedSessions: convertToAPIResourceAccess(userACL.RecordedSessions),
ActiveSessions: convertToAPIResourceAccess(userACL.ActiveSessions),
AuthConnectors: convertToAPIResourceAccess(userACL.AuthConnectors),
Roles: convertToAPIResourceAccess(userACL.Roles),
Users: convertToAPIResourceAccess(userACL.Users),
TrustedClusters: convertToAPIResourceAccess(userACL.TrustedClusters),
Events: convertToAPIResourceAccess(userACL.Events),
Tokens: convertToAPIResourceAccess(userACL.Tokens),
Servers: convertToAPIResourceAccess(userACL.Nodes),
Apps: convertToAPIResourceAccess(userACL.AppServers),
Dbs: convertToAPIResourceAccess(userACL.DBServers),
Kubeservers: convertToAPIResourceAccess(userACL.KubeServers),
AccessRequests: convertToAPIResourceAccess(userACL.AccessRequests),
}

withDetails := &ClusterWithDetails{
Cluster: c,
SuggestedReviewers: caps.SuggestedReviewers,
Expand Down
104 changes: 75 additions & 29 deletions lib/teleterm/services/userpreferences/userpreferences.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,39 @@ import (
"context"

"github.com/gravitational/trace"
"golang.org/x/sync/errgroup"

userpreferencesv1 "github.com/gravitational/teleport/api/gen/proto/go/userpreferences/v1"
api "github.com/gravitational/teleport/gen/proto/go/teleport/lib/teleterm/v1"
)

func Get(ctx context.Context, rootClient Client, leafClient Client) (*api.UserPreferences, error) {
rootPreferencesResponse, err := rootClient.GetUserPreferences(ctx, &userpreferencesv1.GetUserPreferencesRequest{})
if err != nil {
group, groupCtx := errgroup.WithContext(ctx)
var rootPreferencesResponse *userpreferencesv1.GetUserPreferencesResponse
var leafPreferencesResponse *userpreferencesv1.GetUserPreferencesResponse

group.Go(func() error {
res, err := rootClient.GetUserPreferences(groupCtx, &userpreferencesv1.GetUserPreferencesRequest{})
rootPreferencesResponse = res
return trace.Wrap(err)
})

if leafClient != nil {
group.Go(func() error {
res, err := leafClient.GetUserPreferences(groupCtx, &userpreferencesv1.GetUserPreferencesRequest{})
leafPreferencesResponse = res
return trace.Wrap(err)
})
}

if err := group.Wait(); err != nil {
return nil, trace.Wrap(err)
}

rootPreferences := rootPreferencesResponse.GetPreferences()
clusterPreferences := rootPreferences.GetClusterPreferences()

if leafClient != nil {
preferences, err := leafClient.GetUserPreferences(ctx, &userpreferencesv1.GetUserPreferencesRequest{})
if err != nil {
return nil, trace.Wrap(err)
}
clusterPreferences = preferences.GetPreferences().GetClusterPreferences()
if leafPreferencesResponse != nil {
clusterPreferences = leafPreferencesResponse.GetPreferences().GetClusterPreferences()
}

return &api.UserPreferences{
Expand All @@ -48,27 +62,55 @@ func Get(ctx context.Context, rootClient Client, leafClient Client) (*api.UserPr
}

// Update updates the preferences for a given user.
// Only the properties that are set (cluster_preferences, unified_resource_preferences) will be updated.
// Only the properties that are set (cluster_preferences, unified_resource_preferences) are updated.
// When updating the preferences for the root cluster, both unified_resource_preferences
// and cluster_preferences are updated in it.
// When updating the preferences for the leaf cluster, only cluster_preferences are updated
// in the leaf, unified_resource_preferences are always updated in the root.
func Update(ctx context.Context, rootClient Client, leafClient Client, newPreferences *api.UserPreferences) (*api.UserPreferences, error) {
// We have to fetch the full user preferences struct and modify only
// the fields that change.
// Calling `UpsertUserPreferences` with only the modified values would reset
// the rest of the preferences.
rootPreferencesResponse, err := rootClient.GetUserPreferences(ctx, &userpreferencesv1.GetUserPreferencesRequest{})
if err != nil {
getGroup, getGroupCtx := errgroup.WithContext(ctx)
var rootPreferencesResponse *userpreferencesv1.GetUserPreferencesResponse
var leafPreferencesResponse *userpreferencesv1.GetUserPreferencesResponse

getGroup.Go(func() error {
res, err := rootClient.GetUserPreferences(getGroupCtx, &userpreferencesv1.GetUserPreferencesRequest{})
rootPreferencesResponse = res
return trace.Wrap(err)
})

if leafClient != nil {
getGroup.Go(func() error {
res, err := leafClient.GetUserPreferences(getGroupCtx, &userpreferencesv1.GetUserPreferencesRequest{})
leafPreferencesResponse = res
return trace.Wrap(err)
})
}

if err := getGroup.Wait(); err != nil {
return nil, trace.Wrap(err)
}
rootPreferences := rootPreferencesResponse.GetPreferences()

rootPreferences := rootPreferencesResponse.GetPreferences()
var leafPreferences *userpreferencesv1.UserPreferences
if leafClient != nil {
response, err := leafClient.GetUserPreferences(ctx, &userpreferencesv1.GetUserPreferencesRequest{})
if err != nil {
return nil, trace.Wrap(err)
}
leafPreferences = response.GetPreferences()
if leafPreferencesResponse != nil {
leafPreferences = leafPreferencesResponse.GetPreferences()
}

// We do not use errgroup.WithContext since we don't want to cancel
// the other request when one of them fails.
//
// We can run update requests concurrently because the preferences for the root
// cluster and the leaf cluster aren't dependent on each other.
// The preferences for the unified view are always set for the root cluster,
// while pinned resources can be set for either the root or the leaf.
// So if, for example, setting unified view preferences fails,
// we can still update pinned resources for leaf.
upsertGroup := errgroup.Group{}

hasUnifiedResourcePreferencesForRoot := newPreferences.UnifiedResourcePreferences != nil
hasClusterPreferencesForRoot := newPreferences.ClusterPreferences != nil && leafPreferences == nil

Expand All @@ -80,24 +122,28 @@ func Update(ctx context.Context, rootClient Client, leafClient Client, newPrefer
rootPreferences.ClusterPreferences = updateClusterPreferences(rootPreferences.ClusterPreferences, newPreferences.ClusterPreferences)
}

err := rootClient.UpsertUserPreferences(ctx, &userpreferencesv1.UpsertUserPreferencesRequest{
Preferences: rootPreferences,
upsertGroup.Go(func() error {
err := rootClient.UpsertUserPreferences(ctx, &userpreferencesv1.UpsertUserPreferencesRequest{
Preferences: rootPreferences,
})
return trace.Wrap(err)
})
if err != nil {
return nil, trace.Wrap(err)
}
}

hasClusterPreferencesForLeaf := newPreferences.ClusterPreferences != nil && leafPreferences != nil
if hasClusterPreferencesForLeaf {
leafPreferences.ClusterPreferences = updateClusterPreferences(leafPreferences.ClusterPreferences, newPreferences.ClusterPreferences)

err := leafClient.UpsertUserPreferences(ctx, &userpreferencesv1.UpsertUserPreferencesRequest{
Preferences: leafPreferences,
upsertGroup.Go(func() error {
err := leafClient.UpsertUserPreferences(ctx, &userpreferencesv1.UpsertUserPreferencesRequest{
Preferences: leafPreferences,
})
return trace.Wrap(err)
})
if err != nil {
return nil, trace.Wrap(err)
}
}

if err := upsertGroup.Wait(); err != nil {
return nil, trace.Wrap(err)
}

updatedPreferences := &api.UserPreferences{
Expand Down