gravitational / teleport

The easiest, and most secure way to access and protect all of your infrastructure.
https://goteleport.com
GNU Affero General Public License v3.0
17.35k stars 1.74k forks source link

Implement connection test for Connect My Computer node #32206

Closed ravicious closed 9 months ago

ravicious commented 1 year ago
ravicious commented 9 months ago

tl;dr scroll to the bottom

Approaches to adjusting diagnostic messages

The connection test for Connect My Computer will be identical to the SSH connection test, but the diagnostic messages need to be adjusted.

There's a bunch of different approaches that we can choose from.

Assume one possible error per trace type

Assume that during an SSH connection test there's only ever a single possible error per trace type and replace the messages with Connect My Computer equivalents before returning the connection diagnostic to the UI.

For example, when we see that RBAC_PRINCIPAL trace is returned with status FAILED

https://github.com/gravitational/teleport/blob/b0837620c55eb7302b3bdf00cfdd72ef46e8bfab/lib/srv/authhandlers.go#L329-L330

…we replace the message to something more fitting.

Pros

Cons

Use enum values instead of strings for trace messages

This is very similar to the approach described above, with the main difference being that we no longer assume that there's only ever going to be a one possible error per trace type.

Pros

Cons

Replace messages at callsites which add traces

diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go
index 4efccb5b1f..374312332a 100644
--- a/lib/srv/authhandlers.go
+++ b/lib/srv/authhandlers.go
@@ -325,8 +325,15 @@ func (h *AuthHandlers) UserKeyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*s
    // only failed attempts are logged right now
    recordFailedLogin := func(err error) {
        failedLoginCount.Inc()
+       _, isConnectMyComputerNode := h.c.Server.GetInfo().GetLabel(types.ConnectMyComputerNodeOwnerLabel)

        message := fmt.Sprintf("Principal %q is not allowed by this certificate. Ensure your roles grants access by adding it to the 'login' property.", conn.User())
+       if isConnectMyComputerNode {
+           connectMyComputerRoleName := connectmycomputer.GetRoleNameForUser(teleportUser)
+
+           message = fmt.Sprintf("Principal %q is not allowed by this certificate. Ensure that the role %s includes %q in the 'login' property.",
+               conn.User(), connectMyComputerRoleName, conn.User())
+       }
        traceType := types.ConnectionDiagnosticTrace_RBAC_PRINCIPAL

        if trace.IsAccessDenied(err) {

Pros

Cons

Detecting a Connect My Computer node during a connection test

To recognize a Connect My Computer node within lib/srv/authhandlers.go, we can check if the labels of the node include types.ConnectMyComputerNodeOwnerLabel, as shown in the diff above. Each Connect My Computer node is configured with this label and its not meant to be changed by the user.

_, isConnectMyComputerNode := h.c.Server.GetInfo().GetLabel(types.ConnectMyComputerNodeOwnerLabel)

The other place where the connection test happens, lib/client/conntest.SSHConnectionTester, does not have any means to detect a Connect My Computer node. We'd have to add a field to TestConnectionRequest, let's say SSHNodeKind with a special value for Connect My Computer nodes. I considered adding ResourceSubKind, but the backend doesn't have an existing notion of a Connect My Computer node subkind and it'd feel weird to have ResourceKind, which does adhere to backend resource kind, and ResourceSubKind which doesn't.

Fetching server details within SSHConnectionTester and using the same logic as in lib/srv/authhandlers.go doesn't seem to make sense. When we start an SSH connection test from the UI, we already know whether we're performing this test for an SSH node or a Connect My Computer node. So we could just pass an additional param signifying whether Connect My Computer is in play or not.

It's worth noting that we have to enable SSHConnectionTester to detect a Connect My Computer node no matter which approach to adjusting diagnostic messages we pick. SSHConnectionTester both adds messages and returns them to UI, so no matter if we decide to just map the messages to Connect My Computer equivalents or adjust them at callsites, SSHConnectionTester needs to know whether a node is a Connect My Computer node or not.

Verdict

Since SSHConnectionTester needs to be made aware of Connect My Computer anyway, I think the approach with adjusting messages at callsites is best from maintenance perspective. I'm not 100% sure about the SSHNodeKind field that I want to add to TestConnectionRequest, but at least it will be easy to change/extend/rename this field if needed.

marcoandredinis commented 9 months ago

I like the approach :+1:

I think we can get the node's information on SSHConnectionTester. If we can't, then I agree with passing an extra variable. I'm just not sure about using anything-kind because that term is already taken for other things.

Maybe something related with the current flow? resourceTile

ravicious commented 9 months ago

So you'd prefer to just fetch node details in SSHConnectionTester so that we can read them like in lib/srv/authhandlers.go, do I understand that right?

The more I think about it, the more I'm for just passing an extra param since we don't make this decision dynamically, we know upfront what kind of test we'd like to perform.

I like the idea with resourceTile instead of sshNodeKind. For now I'd probably add it to the struct, but pass it only in Connect My Computer flow.

marcoandredinis commented 9 months ago

Yeah, that was my suggestion.

But I think that's also fine to just include the new field :+1:

ravicious commented 9 months ago

Regarding resourceTile, this makes the relationship between conntest and Discover tighter, but I just saw that nowadays we have structs like ExternalAuditStorageConnectionTester which are not related to Discover.

For now I'll use resourceTile, but let's keep the above in mind and decide when I submit the PR.