-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Conversation
I'll make full PR after adding tests. |
There was a problem hiding this 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
// calculating possible users for db is only done in verbose mode. | ||
displayUsersForDb := func(database types.Database) string { | ||
return "" | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
usersOk := make([]string, 0) | ||
usersBad := make([]string, 0) |
There was a problem hiding this comment.
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/db.go
Outdated
allUsers := make([]string, 0) | ||
allUsers = append(allUsers, allowedUsers...) | ||
allUsers = append(allUsers, deniedUsers...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
tool/tsh/db.go
Outdated
}) | ||
|
||
if err != nil { | ||
log.Debugf("Error while calculating available db users: %v", err) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
err = client.RetryWithRelogin(cf.Context, tc, func() error { | ||
proxy, err := tc.ConnectToProxy(cf.Context) | ||
if err != nil { | ||
return trace.Wrap(err) | ||
} |
There was a problem hiding this comment.
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.
9e7cf58
to
8db0401
Compare
Per prior requests, the core enumeration logic is now in |
tsh db ls
lists available db users.
@smallinsky added examples, as discussed offline the other day. |
{ | ||
name: "developer allowed any username in stage database except superuser", | ||
roles: services.RoleSet{roleDevStage, roleDevProd}, | ||
database: dbStage, | ||
result: "[* dev], except: [superuser]", |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
// 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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
.
PTAL @r0mant @smallinsky |
By the way, could you backport it to v9? Teleterm is planned to release in 9.2 and it'll need these changes. |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
@@ -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 |
There was a problem hiding this comment.
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:
allowedDeniedMap map[string]bool | |
allowedUsers []string | |
deniedUsers []string |
For example the (result *EnumerationResult) filtered
can be dropped.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
wildcardAllowed := false | ||
wildcardDenied := false | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Yep, I'll backport to v9 and v8. |
@klizhentas, @r0mant |
Co-authored-by: Marek Smoliński <[email protected]>
There was a problem hiding this 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
* Show available db users in "tsh db ls". Co-authored-by: Marek Smoliński <[email protected]>
* Show available db users in "tsh db ls". Co-authored-by: Marek Smoliński <[email protected]>
* Show available db users in "tsh db ls". Co-authored-by: Marek Smoliński <[email protected]>
* Show available db users in "tsh db ls". Co-authored-by: Marek Smoliński <[email protected]>
proxy, err := tc.ConnectToProxy(cf.Context) | ||
if err != nil { | ||
return trace.Wrap(err) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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()
.
Fixes #9181.
Examples: