-
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
Fix tsh db ls
for remote clusters.
#12281
Conversation
GetCurrentUser returns current user as seen by the server. Useful especially in the context of remote clusters which perform role and trait mapping.
Attempt to resolve remote roles and traits. Fail gracefully if this isn't possible.
lib/auth/auth_with_roles.go
Outdated
if ok { | ||
return usr, nil | ||
} | ||
return nil, trace.Errorf("internal error: unexpected type") |
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.
return nil, trace.Errorf("internal error: unexpected type") | |
return nil, trace.BadParameter("expected types.User when fetching current user information, got %T", xxx) |
tool/tsh/db.go
Outdated
roles = user.GetRoles() | ||
traits = user.GetTraits() | ||
} else { | ||
log.Debugf("cluster.GetCurrentUser failed: %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.
log.Debugf("cluster.GetCurrentUser failed: %v", err) | |
log.Debugf("Failed to fetch current user information: %v.", err) |
tool/tsh/db.go
Outdated
if err != nil { | ||
return trace.Wrap(err) | ||
log.Debugf("services.FetchRoles failed: %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.
log.Debugf("services.FetchRoles failed: %v", err) | |
log.Debugf("Failed to fetch user roles: %v.", err) |
@@ -1742,6 +1742,16 @@ 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) { |
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.
Without any additional RBAC checks here, I'm a little worried about privilege escalation since we're potentially letting the root's users expand the scope of what they can learn about the leaf cluster. For example, role names - currently they are not available anywhere in the root so users can only see them if they have proper permissions.
I think we should require at least permissions to either read the user's roles or list roles prior to returning this. If the leaf doesn't allow root users view the roles, they're not also gonna be able to see "allowed database users" - which makes sense.
Any other fields are a part of User object we may want to protect?
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.
As long as the user has the role, they are allowed to see its definition (from here):
// GetRole returns role by name
func (a *ServerWithRoles) GetRole(ctx context.Context, name string) (types.Role, error) {
// Current-user exception: we always allow users to read roles
// that they hold. This requirement is checked first to avoid
// misleading denial messages in the logs.
if !apiutils.SliceContainsStr(a.context.User.GetRoles(), name) {
if err := a.action(apidefaults.Namespace, types.KindRole, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
return a.authServer.GetRole(ctx, name)
}
We don't treat the names of roles as secret. The user would also only get the roles they got from mapping in trusted_cluster
resource, which are explicitly given and likely very few.
If we were to do any RBAC checks here, I'm not sure what would they be?
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 alternative approach would be to move the db user list logic from tsh
flow to a backend endpoint. But not sure if we should do it.
Since roles name and trails are public available for a user logged into root cluster not sure what is the reason to hide leaf
cluster roles and traits from a user. Thought I think that it would be good idea for now to trim userV2 content and return only to trails and roles name in case of GetCurrentUser
preventing the case where userV2 will be extended with some sensitive data.
@Tener
Have you checked if the GetUser can be leveraged here instead of introducing a new GetCurrentUser endpoint. At first glace it looks that authorizeRemoteUser
set the correct remote user identity:
teleport/lib/auth/permissions.go
Line 269 in 2292be1
user.SetRoles(roleNames) |
teleport/lib/auth/permissions.go
Line 292 in 2292be1
Groups: user.GetRoles(), |
where GetUser already allows to get user's own identity on remote cluster:
func (a *ServerWithRoles) GetUser(name string, withSecrets bool) (types.User, error) {
...
// if secrets are not being accessed, let users always read
// their own info.
if err := a.currentUserAction(name); err != nil {
// not current user, perform normal permission check.
if err := a.action(apidefaults.Namespace, types.KindUser, types.VerbRead); err != nil {
return nil, trace.Wrap(err)
}
}
}
return a.authServer.Identity.GetUser(name, withSecrets)
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.
Have you checked if the GetUser can be leveraged here instead of introducing a new GetCurrentUser endpoint.
Yeah, that was the first thing I tried. GetUser
and GetUsers
work against real users, provisioned in one way or another.
So a.authServer.Identity.GetUser(name, withSecrets)
goes to backend, which is different from a.context.User
or a.context.Identity
.
Also, the remote users will never satisfy "look at self" check, because we assume no collisions happen:
teleport/lib/auth/permissions.go
Lines 260 to 262 in 2292be1
// The user is prefixed with "remote-" and suffixed with cluster name with | |
// the hope that it does not match a real local user. | |
user, err := types.NewUser(fmt.Sprintf("remote-%v-%v", u.Username, u.ClusterName)) |
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.
@reedloden @russjones Any objections to exposing the user's own leaf cluster identity in this way, provided we add RBAC checks for the role names? It's needed to fetch leaf's role names so tsh can show "allowed database users" for leaf cluster's databases (needed by Teleport Connect also).
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.
@reedloden @russjones Any objections to exposing the user's own leaf cluster identity in this way, provided we add RBAC checks for the role names? It's needed to fetch leaf's role names so tsh can show "allowed database users" for leaf cluster's databases (needed by Teleport Connect also).
@reedloden @russjones can you take a look as per @r0mant request? It would be good to fix this issue as currently the feature is degraded for remote clusters.
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.
@reedloden @russjones PTAL?
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 talked with @russjones offline about this and there's no objections to exposing user's remote identities to themselves as long as we require read permissions on roles (basically, what I suggested originally). Can you apply the changes? Then we can merge.
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 talked with @russjones offline about this and there's no objections to exposing user's remote identities to themselves as long as we require read permissions on roles (basically, what I suggested originally). Can you apply the changes? Then we can merge.
Thanks for checking, I'll make the change.
@ravicious FYI, pretty sure Teleport Connect will need a similar fix. |
It looks like for Teleport Connect it's a matter of modifying teleport/lib/teleterm/clusters/cluster_databases.go Lines 128 to 143 in d9deaff
We need to get ahold of auth client, in a similar manner as we already do here: teleport/lib/teleterm/clusters/cluster_kubes.go Lines 36 to 48 in d9deaff
If Do you mind adding this yourself or would you rather have me implement this? I could definitely test the change with Connect if you've never built it yourself. |
How do you ensure that for remote clusters |
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 patch itself in terms of quality seems okay but this feels like a bit of an odd solution to problems we've solved differently in other places. For non-db stuff like sessions, we move the listing logic to a backend endpoint. This exposes minimal information and is the common pattern used as far as I can tell.
Here, that seems like it would involve hitting the current cluster as specified by the tsh profile/--cluster flag and asking it to do the listing instead.
If we decide to stick with the current approach presented here, may I suggest renaming the GetCurrentUser
endpoint to something else like GetMappedUser
?
For maximum compatibility, I wanted to avoid adding new endpoints where it wasn't needed.
There were some other names I considered though, e.g. |
// 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) |
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.
Moving the conversation about Teleport Connect so that it doesn't pollute the PR comments.
How do you ensure that for remote clusters c.status.Roles is populated properly?
What I meant in my original comment is that once we get ahold of the auth client in GetAllowedDatabaseUsers
before calling FetchRoles
, we will be able to use the same logic as tsh uses here, won't we?
We'd call authClient.GetCurrentUser
and if that fails we'll just use data from c.status
.
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.
Ah, ok, I misunderstood. But yeah, I think that replicating this logic would work just fine.
@r0mant @ravicious @xacrimon @smallinsky kind reminder. |
@Tener |
So, basically this PR without It feels wrong that we are supposed to hide the permissions the user has from that very same user. I have never seen that sentiment anywhere in the codebase and I find it very likely that at least part of that information may already be available in some contexts. |
Yes, By this fix we can quickly fix the regression even if it is incomplete. |
@smallinsky hotfix: #12318 |
tsh db ls
for remote clusters.
I think it's ok, the only "controversial" part IMO is what I commented on before - that is, potentially exposing more information to the user than what they currently have access to according to their role mapping. I understand role names don't technically reveal much but currently the user does not have any means of even knowing what role names the leaf has unless they have "list" permissions on them. Which is why I suggested adding that RBAC check. We can always relax it later if it turns out to be cumbersome and limit usefulness of this feature. |
@r0mant I've added the access check in commit 984397f. The check is fairly simple, and follows the pattern seen elsewhere of reusing teleport/lib/auth/auth_with_roles.go Lines 1930 to 1937 in 984397f
I think it is noteworthy that with the current code there is no way for the check to actually fail. This is because teleport/lib/auth/auth_with_roles.go Lines 2930 to 2939 in 984397f
It can be argued, however, that this is a reasonable defensive coding. The particulars of the permission checks in |
* 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.
* 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.
* 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.
* 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.
#12281 fixed fetching db users for remote clusters for `tsh db ls`. #13617 applied the same fix to `tsh db ls --all` and extracted the `fetchRoleSet` function. This commit extracts it to `lib/client` so that we can reuse it in `lib/teleterm`. Other than accepting `log` as an argument, nothing was changed about that function.
The
tsh db ls
command needs to process the definition of roles for the current user along with their traits. For local clusters, this information is available in their profile. When connecting to remote clusters, however, the user is mapped to a distinct set of roles and traits. This is done inauthorizeRemoteUser
:teleport/lib/auth/permissions.go
Lines 214 to 215 in 2292be1
Using the roles and traits from the user profile is wrong in the context of the remote cluster; the roles may not even exist or they may not be assigned to the user post-translation.
The fix has several layers. First, we introduce
.GetCurrentUser()
method. This informs the user of how the server sees them after authentication, making the effective set of roles and traits explicit. Using this informationtsh
can request correct roles and perform the calculation it needs.For compatibility reasons, we fall back to roles and traits from the profile if
.GetCurrentUser()
is not available. This has a risk of inaccurate information being presented which may lead to.FetchRoles()
call failing (e.g. user asks about roles they have no access to or which don't exist). In this case, we fall back to displaying "(unknown)" as the list of available users.Fixes #12239
Caused by #10458