Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make tsh db ls lists available db users. #10458

Merged
merged 8 commits into from
Apr 14, 2022
Merged

Make tsh db ls lists available db users. #10458

merged 8 commits into from
Apr 14, 2022

Conversation

Tener
Copy link
Contributor

@Tener Tener commented Feb 18, 2022

Fixes #9181.

Examples:

> tsh db ls
Name  Description   Allowed Users Labels  Connect
----- ------------- ------------- ------- -------
redis Redis example (none)        env=dev

> tsh db ls
Name  Description   Allowed Users Labels  Connect
----- ------------- ------------- ------- -------
redis Redis example [*]           env=dev

> tsh db ls
Name  Description   Allowed Users      Labels  Connect
----- ------------- ------------------ ------- -------
redis Redis example [*], except: [bob] env=dev

> tsh db ls
Name  Description   Allowed Users          Labels  Connect
----- ------------- ---------------------- ------- -------
redis Redis example [* baz], except: [bob] env=dev

> tsh db ls
Name  Description   Allowed Users Labels  Connect
----- ------------- ------------- ------- -------
redis Redis example [baz]         env=dev

> tsh db ls --verbose
Name  Description   Protocol Type        URI                     Allowed Users Labels                                  Connect Expires
----- ------------- -------- ----------- ----------------------- ------------- --------------------------------------- ------- -------------------
redis Redis example redis    self-hosted rediss://localhost:6666 [* baz]       env=dev,teleport.dev/origin=config-file         Jan  1 00:00:00 UTC

@Tener Tener requested a review from r0mant February 18, 2022 15:46
@Tener
Copy link
Contributor Author

Tener commented Feb 18, 2022

I'll make full PR after adding tests.

@Tener Tener marked this pull request as ready for review February 21, 2022 14:11
@github-actions github-actions bot added the tsh tsh - Teleport's command line tool for logging into nodes running Teleport. label Feb 21, 2022
@github-actions github-actions bot requested a review from ravicious February 21, 2022 14:12
Copy link
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some minor questions, but LGTM in general!

tool/tsh/db.go Outdated Show resolved Hide resolved
tool/tsh/db.go Outdated Show resolved Hide resolved
tool/tsh/db_test.go Outdated Show resolved Hide resolved
tool/tsh/db.go Outdated
Comment on lines 62 to 61
// calculating possible users for db is only done in verbose mode.
displayUsersForDb := func(database types.Database) string {
return ""
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just make it a default behavior? Add another column to the table with allowed users.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, it makes the code cleaner still. Although given the requested moves it will need to be reworked anyway.

tool/tsh/db.go Outdated
return nil
}

// getUsersForDb finds allowed and denied users given user's effective roleSet and database in consideration. The result is a user-readable string.
func getUsersForDb(roleSet services.RoleSet, database types.Database) string {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of implementing this in the CLI, this needs to be a part of the API. This way UI will also be able to reuse it for example. I would either make this a part of the GetDatabases API, or introduce a separate API endpoint.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it more complex to implement, but I can see the benefits. I'll look into this after v9 is out.

Copy link
Collaborator

@r0mant r0mant Mar 4, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tener I've been thinking, it probably doesn't even need to be an actual API - but we should at least move this function somewhere it can be reused e.g. to lib/services package. This way web API will also be able to use it since it has RoleSet and Database. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@r0mant sounds reasonable; I generally agree this should move towards the application's core - either as a callable service or as part of the core library. I'll see which option feels better once I get back to this.

tool/tsh/db.go Outdated Show resolved Hide resolved
tool/tsh/db.go Outdated
Comment on lines 127 to 128
usersOk := make([]string, 0)
usersBad := make([]string, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we come up with names that are more descriptive than "ok" and "bad"?

tool/tsh/tsh.go Outdated Show resolved Hide resolved
tool/tsh/db.go Outdated
Comment on lines 123 to 125
allUsers := make([]string, 0)
allUsers = append(allUsers, allowedUsers...)
allUsers = append(allUsers, deniedUsers...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
allUsers := make([]string, 0)
allUsers = append(allUsers, allowedUsers...)
allUsers = append(allUsers, deniedUsers...)
allUsers := append(allowedUsers, deniedUsers...)

Also, it feels like the above function can just return all users right away since allowed/denied aren't used independently here.

@r0mant r0mant requested a review from smallinsky February 24, 2022 02:17
tool/tsh/db.go Outdated
})

if err != nil {
log.Debugf("Error while calculating available db users: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the flow should processed when a user is unable to connect to the proxy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially this can also be caused by a lack of permissions. But I'm pretty sure this part will change by moving the calculation logic to the server and exposing it via API.

tool/tsh/db.go Outdated
Comment on lines 73 to 77
err = client.RetryWithRelogin(cf.Context, tc, func() error {
proxy, err := tc.ConnectToProxy(cf.Context)
if err != nil {
return trace.Wrap(err)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: move to a separate function also the logic can be splitted fetching roles, making a displayUsersForDb function.

@Tener Tener force-pushed the tener/db-ls-users-pr branch from 9e7cf58 to 8db0401 Compare April 6, 2022 13:58
@Tener Tener requested review from r0mant, ravicious and smallinsky April 6, 2022 13:59
@Tener
Copy link
Contributor Author

Tener commented Apr 6, 2022

Per prior requests, the core enumeration logic is now in role.go and tsh db ls is merely a client of that logic. Should be good for reuse in teleterm.

@Tener Tener changed the title In verbose mode "tsh db ls" lists available users. Make tsh db ls lists available db users. Apr 6, 2022
@Tener
Copy link
Contributor Author

Tener commented Apr 7, 2022

@smallinsky added examples, as discussed offline the other day.

Comment on lines +1514 to +1518
{
name: "developer allowed any username in stage database except superuser",
roles: services.RoleSet{roleDevStage, roleDevProd},
database: dbStage,
result: "[* dev], except: [superuser]",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this scenario, shouldn't the result be just [*]?

The dev username comes from roleDevProd, it seems like it shouldn't have any bearing on users for dbStage.

Copy link
Contributor Author

@Tener Tener Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a subtle point and I'm happy that we have this test. The original context for this feature is important: we have a permissive role (all users allowed: *) and the users want a hint on what users they can use. We go looking for possible usernames in other roles and also allow additional usernames to be provided.

Since we discovered that dev is a possible login, we include it here.

Comment on lines +922 to +924
// In cases where * is listed in set of allowed users, it may be hard for users to figure out the expected username.
// For this reason the parameter extraUsers provides an extra set of users to be checked against RoleSet.
// This extra set of users may be sourced e.g. from user connection history.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this added with Teleterm in mind? I hope we actually get to use this. 🙊 But I'll think about how to utilize this once I actually get around to incorporating that in Teleterm.

Copy link
Contributor Author

@Tener Tener Apr 7, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, Teleterm was the planned user for this parameter. I guessed that it has a mechanism for storing recently used resources etc.

Alternatively, this may be a feature of the database itself: on successful auth, store the username which can be queried as a property of the database. This has the benefit of sharing the available usernames across all people who can access that particular database in Teleport.

The second mechanism is perhaps more robust and can be shared with tsh.

@Tener
Copy link
Contributor Author

Tener commented Apr 11, 2022

PTAL @r0mant @smallinsky

@ravicious
Copy link
Member

By the way, could you backport it to v9? Teleterm is planned to release in 9.2 and it'll need these changes.

Copy link
Contributor

@smallinsky smallinsky left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some minor comments otherwise LGTM.

@@ -862,6 +862,110 @@ func NewRoleSet(roles ...types.Role) RoleSet {
// RoleSet is a set of roles that implements access control functionality
type RoleSet []types.Role

// EnumerationResult is a result of enumerating a role set against some property, e.g. allowed names or logins.
type EnumerationResult struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This EnumerationResult is quite generic name for the role package. For example roles.EnumerationResult suggest that roles are enumerated.

Copy link
Contributor Author

@Tener Tener Apr 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any suggestions? RoleSetEnumerationResult is bit too long for my tastes.

lib/services/role.go Outdated Show resolved Hide resolved
@@ -862,6 +862,110 @@ func NewRoleSet(roles ...types.Role) RoleSet {
// RoleSet is a set of roles that implements access control functionality
type RoleSet []types.Role

// EnumerationResult is a result of enumerating a role set against some property, e.g. allowed names or logins.
type EnumerationResult struct {
allowedDeniedMap map[string]bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: splitting in two separate list instead of aggregating a user into single map should simplify the implemeantion:

Suggested change
allowedDeniedMap map[string]bool
allowedUsers []string
deniedUsers []string

For example the (result *EnumerationResult) filtered can be dropped.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a map to deal with duplicates, so either we do it in one go or we need to deduplicate afterward, which makes the code more complex, not less.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The login duplication are already removed in the users = apiutils.Deduplicate(append(users,extraUsers) call

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The login duplication are already removed in the users = apiutils.Deduplicate(append(users,extraUsers) call

This is done outside of EnumerationResult code.

I can switch to two slices, but that makes it possible to have an internal representation which is inconsistent. The easiest way to efficiently remove duplicates is with map.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. This was just a suggestion. Let's leave the current form.

Comment on lines +931 to +933
wildcardAllowed := false
wildcardDenied := false

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be moved outside the for loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On the contrary: it must be inside the loop, since these two bools refer to particular role.

@Tener
Copy link
Contributor Author

Tener commented Apr 11, 2022

By the way, could you backport it to v9? Teleterm is planned to release in 9.2 and it'll need these changes.

Yep, I'll backport to v9 and v8.

@smallinsky
Copy link
Contributor

@klizhentas, @r0mant
Could you take a look at UI tsh db ls changes described in PR description .

Copy link
Collaborator

@r0mant r0mant left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Tener @smallinsky I like the UX :shipit:

@Tener Tener enabled auto-merge (squash) April 14, 2022 08:32
@Tener Tener merged commit 6bd7bf9 into master Apr 14, 2022
@Tener Tener deleted the tener/db-ls-users-pr branch April 14, 2022 09:45
Tener added a commit that referenced this pull request Apr 14, 2022
* Show available db users in "tsh db ls".

Co-authored-by: Marek Smoliński <[email protected]>
Tener added a commit that referenced this pull request Apr 14, 2022
* Show available db users in "tsh db ls".

Co-authored-by: Marek Smoliński <[email protected]>
Tener added a commit that referenced this pull request Apr 19, 2022
* Show available db users in "tsh db ls".

Co-authored-by: Marek Smoliński <[email protected]>
Tener added a commit that referenced this pull request Apr 20, 2022
* Show available db users in "tsh db ls".

Co-authored-by: Marek Smoliński <[email protected]>
@webvictim webvictim mentioned this pull request Jun 8, 2022
Comment on lines +54 to +57
proxy, err := tc.ConnectToProxy(cf.Context)
if err != nil {
return trace.Wrap(err)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fixing a bug in Connect where we prematurely close the proxy client when fetching db users. I was looking at how tsh does things (since it was working fine in tsh db ls) and I landed here and noticed that we don't seem to close the proxy client here at all.

Shouldn't we have defer proxy.Close() at the end here just as we have defer cluster.Close() below?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix in Connect I was working on: #14230.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably should. There are, in fact, a few other places in codebase that are missing the deferred .Close() call on the result of ConnectToProxy().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tsh tsh - Teleport's command line tool for logging into nodes running Teleport.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Show Available DB Users for a Database with tsh
4 participants