Skip to content

Commit

Permalink
Fix tsh db ls for remote clusters. (#12281)
Browse files Browse the repository at this point in the history
* New method: GetCurrentUser().

GetCurrentUser returns current user as seen by the server.
Useful especially in the context of remote clusters which perform role and trait mapping.

* Fix "tsh db ls" for remote clusters.

Attempt to resolve remote roles and traits. Fail gracefully if this isn't possible.
  • Loading branch information
Tener authored May 23, 2022
1 parent 2bd4f27 commit 202c431
Show file tree
Hide file tree
Showing 8 changed files with 753 additions and 608 deletions.
10 changes: 10 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,16 @@ func (c *Client) GetUser(name string, withSecrets bool) (types.User, error) {
return user, nil
}

// GetCurrentUser returns current user as seen by the server.
// Useful especially in the context of remote clusters which perform role and trait mapping.
func (c *Client) GetCurrentUser(ctx context.Context) (types.User, error) {
currentUser, err := c.grpc.GetCurrentUser(ctx, &empty.Empty{})
if err != nil {
return nil, trail.FromGRPC(err)
}
return currentUser, nil
}

// GetUsers returns a list of users.
// withSecrets controls whether authentication details are returned.
func (c *Client) GetUsers(withSecrets bool) ([]types.User, error) {
Expand Down
1,256 changes: 649 additions & 607 deletions api/client/proto/authservice.pb.go

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions api/client/proto/authservice.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1775,6 +1775,9 @@ service AuthService {

// GetUser gets a user resource by name.
rpc GetUser(GetUserRequest) returns (types.UserV2);
// GetCurrentUser returns current user as seen by the server.
// Useful especially in the context of remote clusters which perform role and trait mapping.
rpc GetCurrentUser(google.protobuf.Empty) returns (types.UserV2);
// GetUsers gets all current user resources.
rpc GetUsers(GetUsersRequest) returns (stream types.UserV2);
// CreateUser inserts a new user entry to a backend.
Expand Down
19 changes: 19 additions & 0 deletions lib/auth/auth_with_roles.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"time"

"github.com/coreos/go-semver/semver"

"github.com/gravitational/teleport"
"github.com/gravitational/teleport/api/client"
"github.com/gravitational/teleport/api/client/proto"
Expand Down Expand Up @@ -1953,6 +1954,24 @@ func (a *ServerWithRoles) GetUser(name string, withSecrets bool) (types.User, er
return a.authServer.Identity.GetUser(name, withSecrets)
}

// GetCurrentUser returns current user as seen by the server.
// Useful especially in the context of remote clusters which perform role and trait mapping.
func (a *ServerWithRoles) GetCurrentUser(ctx context.Context) (types.User, error) {
// check access to roles
for _, role := range a.context.User.GetRoles() {
_, err := a.GetRole(ctx, role)
if err != nil {
return nil, trace.Wrap(err)
}
}

usrRes := a.context.User.WithoutSecrets()
if usr, ok := usrRes.(types.User); ok {
return usr, nil
}
return nil, trace.BadParameter("expected types.User when fetching current user information, got %T", usrRes)
}

// DeleteUser deletes an existng user in a backend by username.
func (a *ServerWithRoles) DeleteUser(ctx context.Context, user string) error {
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbDelete); err != nil {
Expand Down
4 changes: 4 additions & 0 deletions lib/auth/clt.go
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,10 @@ type IdentityService interface {
// GetUser returns user by name
GetUser(name string, withSecrets bool) (types.User, error)

// GetCurrentUser returns current user as seen by the server.
// Useful especially in the context of remote clusters which perform role and trait mapping.
GetCurrentUser(ctx context.Context) (types.User, error)

// CreateUser inserts a new entry in a backend.
CreateUser(ctx context.Context, user types.User) error

Expand Down
17 changes: 17 additions & 0 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,23 @@ func (g *GRPCServer) GetUser(ctx context.Context, req *proto.GetUserRequest) (*t
return v2, nil
}

func (g *GRPCServer) GetCurrentUser(ctx context.Context, req *empty.Empty) (*types.UserV2, error) {
auth, err := g.authenticate(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
user, err := auth.ServerWithRoles.GetCurrentUser(ctx)
if err != nil {
return nil, trace.Wrap(err)
}
v2, ok := user.(*types.UserV2)
if !ok {
log.Warnf("expected type services.UserV2, got %T for user %q", user, user.GetName())
return nil, trace.Errorf("encountered unexpected user type")
}
return v2, nil
}

func (g *GRPCServer) GetUsers(req *proto.GetUsersRequest, stream proto.AuthService_GetUsersServer) error {
auth, err := g.authenticate(stream.Context())
if err != nil {
Expand Down
30 changes: 30 additions & 0 deletions lib/auth/tls_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -910,6 +910,36 @@ func (s *TLSSuite) TestReadOwnRole(c *check.C) {
fixtures.ExpectAccessDenied(c, err)
}

func TestGetCurrentUser(t *testing.T) {
ctx := context.Background()
srv := newTestTLSServer(t)

user1, _, err := CreateUserAndRole(srv.Auth(), "user1", []string{"user1"})
require.NoError(t, err)

client1, err := srv.NewClient(TestIdentity{I: LocalUser{Username: user1.GetName()}})
require.NoError(t, err)

currentUser, err := client1.GetCurrentUser(ctx)
require.NoError(t, err)
require.Equal(t, &types.UserV2{
Kind: "user",
SubKind: "",
Version: "v2",
Metadata: types.Metadata{
Name: "user1",
Namespace: "default",
Description: "",
Labels: nil,
Expires: nil,
ID: 12,
},
Spec: types.UserSpecV2{
Roles: []string{"user:user1"},
},
}, currentUser)
}

func (s *TLSSuite) TestAuthPreference(c *check.C) {
clt, err := s.server.NewClient(TestAdmin())
c.Assert(err, check.IsNil)
Expand Down
22 changes: 21 additions & 1 deletion tool/tsh/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,27 @@ func onListDatabases(cf *CLIConf) error {
return trace.Wrap(err)
}

roleSet, err := services.FetchRoles(profile.Roles, cluster, profile.Traits)
// get roles and traits. default to the set from profile, try to get up-to-date version from server point of view.
roles := profile.Roles
traits := profile.Traits

// GetCurrentUser() may not be implemented, fail gracefully.
user, err := cluster.GetCurrentUser(cf.Context)
if err == nil {
roles = user.GetRoles()
traits = user.GetTraits()
} else {
log.Debugf("Failed to fetch current user information: %v.", err)
}

// get the role definition for all roles of user.
// this may only fail if the role which we are looking for does not exist, or we don't have access to it.
// example scenario when this may happen:
// 1. we have set of roles [foo bar] from profile.
// 2. the cluster is remote and maps the [foo, bar] roles to single role [guest]
// 3. the remote cluster doesn't implement GetCurrentUser(), so we have no way to learn of [guest].
// 4. services.FetchRoles([foo bar], ..., ...) fails as [foo bar] does not exist on remote cluster.
roleSet, err := services.FetchRoles(roles, cluster, traits)
if err != nil {
log.Debugf("Failed to fetch user roles: %v.", err)
}
Expand Down

0 comments on commit 202c431

Please sign in to comment.