Skip to content

Add file transfer support to Connect#16880

Merged
gzdunek merged 29 commits intomasterfrom
gzdunek/add-connect-sftp-support
Oct 10, 2022
Merged

Add file transfer support to Connect#16880
gzdunek merged 29 commits intomasterfrom
gzdunek/add-connect-sftp-support

Conversation

@gzdunek
Copy link
Copy Markdown
Contributor

@gzdunek gzdunek commented Sep 30, 2022

webapps counterpart: gravitational/webapps#1225

This PR adds new TransferFile server-side stream to tshd to run SFTP file transfer.

The TransferFile stream itself does not physically transfer files, but delegates this task to sftp module. In the request we only need to specify the params like: source, destination, login, hostname. We don't support recursive upload/download. Server-side streaming is used to report the transfer progress to the UI.
To allow sending the progress via gRPC, I changed config.ProgressWriter to be a function that is called with the currently transferred file in the argument. Thanks to this, tsh scp and Connect can specify their own progress writers.

Comment on lines +53 to +73
clusterServers, err := proxyClient.FindNodesByFilters(server.Context(), proto.ListResourcesRequest{
Namespace: defaults.Namespace,
})

if err != nil {
return trace.Wrap(err)
}

var foundServer types.Server
for _, clusterServer := range clusterServers {
if clusterServer.GetName() == request.GetServerId() {
foundServer = clusterServer
break
}
}

if foundServer == nil {
return trace.BadParameter("Requested server does not exist")
}

err = c.clusterClient.TransferFiles(server.Context(), request.GetLogin(), foundServer.GetHostname()+":0", config)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'm generally unsure about this part.
Is there a better way to provide host address to clusterClient.TransferFiles? Perhaps instead of sending serverId from Connect I should send the hostname? By doing this I wouldn't have to connect to proxy (clusterClient.TransferFiles also runs this function so it takes a good few seconds all together).
Additionally, downloading a file from a leaf cluster fails with an error No proxy address specified, missed --proxy flag?, did I miss something obvious?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll try to look into it tomorrow, I probably won't have time for that today unfortunately.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I looked into this and I think you're right, passing the hostname seems to be the way to go. I checked it and that's how both the Web UI and tsh does it. Well, in tsh you can specify the address instead of the hostname as well.

As for the problem with leaf clusters: it looks like the server URI that is used for the TransferFile RPC is wrong. Mine said /clusters/teleport-local-leaf/servers/4bbde2c9-b805-4dd8-8032-a0bed162d85b, suggesting that teleport-local-leaf is a root cluster but it's a leaf cluster.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for help with this, works perfectly!

The only thing that puzzles me is request.GetHostname()+":0".
Without a port, it fails with an error failed connecting to node teleport_root. invalid format for proxy request: "proxy:teleport_root@default@teleport_root", expected 'proxy:host:port@cluster'.
I assume I have to add :0 to the hostname, so the the node address is resolved automatically?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't know the details but yeah, that seems to be the case. tsh ssh needs to support both commands:

tsh scp file.text root@mbp.teleport-local:/tmp/
tsh scp -P 3022 file.text root@127.0.0.1:/tmp/

So I imagine in case port is set to 0, tsh tries to resolve the hostname to one of the available SSH servers.

Comment thread lib/client/api.go Outdated
Comment thread lib/sshutils/sftp/sftp.go Outdated
Comment thread lib/teleterm/daemon/daemon.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go
// ClusterLogin logs out a user from cluster
rpc Logout(LogoutRequest) returns (EmptyResponse);
// TransferFile downloads/uploads a file
rpc TransferFile(FileTransferRequest) returns (stream FileTransferProgress);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

stream 🚀

Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Copy link
Copy Markdown
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.

I'm short on time today, I reviewed as much as I could. I didn't have the time to answer your questions, I look into that and the rest of the code tomorrow.

Comment thread lib/teleterm/apiserver/handler/handler_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment on lines +53 to +73
clusterServers, err := proxyClient.FindNodesByFilters(server.Context(), proto.ListResourcesRequest{
Namespace: defaults.Namespace,
})

if err != nil {
return trace.Wrap(err)
}

var foundServer types.Server
for _, clusterServer := range clusterServers {
if clusterServer.GetName() == request.GetServerId() {
foundServer = clusterServer
break
}
}

if foundServer == nil {
return trace.BadParameter("Requested server does not exist")
}

err = c.clusterClient.TransferFiles(server.Context(), request.GetLogin(), foundServer.GetHostname()+":0", config)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'll try to look into it tomorrow, I probably won't have time for that today unfortunately.

Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
"time"
)

func (c *Cluster) TransferFile(request *api.FileTransferRequest, server api.TerminalService_TransferFileServer) error {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See what Alan had to say about passing streams around. Though I suppose at this point it might be too late to introduce significant refactors here but I also haven't given it too much thought.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, there is no need to pass the entire server, I replaced it with sendProgress function. I was wondering if this function shouldn't be called send, but perhaps longer name gives more context?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was wondering if this function shouldn't be called send, but perhaps longer name gives more context?

Yeah, sendProgress sounds much better imho.

Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Copy link
Copy Markdown
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.

I need to look more closely at GrpcFileTransferProgress tomorrow but that's about what's left for me to review in this PR.

Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/client/api.go Outdated
Comment thread lib/sshutils/sftp/sftp.go
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Copy link
Copy Markdown
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.

Confirmed that upload/download works correctly after refreshing certs.

I left some minor comments but the rest looks fine.

Comment thread lib/sshutils/sftp/sftp.go Outdated
Comment thread lib/teleterm/api/proto/v1/service.proto
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
Comment thread lib/teleterm/clusters/cluster_file_transfer.go Outdated
@gzdunek gzdunek marked this pull request as ready for review October 6, 2022 11:13
}

type fileTransferProgress struct {
sendProgress func(progress *api.FileTransferProgress) error
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: since it's used multiple times here, I think you should create a type for this function prototype. Maybe progressSender?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, I called it FileTransferProgressSender, because it is also used in daemon.go

@strideynet strideynet self-requested a review October 10, 2022 08:53
}
}

message FileTransferRequest {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It feels a little confusing to have source, destination and direction, and it isn't immediately obvious how to use this without looking at the implementation. Is there any comments we could add to this message or the file transfer direction enums to clarify the behaviour here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added.

var configErr error

direction := request.GetDirection()
if direction == api.FileTransferDirection_FILE_TRANSFER_DIRECTION_UNSPECIFIED {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It's worth being more careful with gRPC enums. If for some reason, a newer client was connected to this service, and that client sent a new direction not yet implemented in the server, this would result in a nil config. Whilst this is unlikely here since the server and the client are pretty tightly bound here, I think it's a good habit to have. Switching to a case statement with a default is probably safer.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good idea, switched to switch/case statement and moved to a new function.

Copy link
Copy Markdown
Contributor

@strideynet strideynet left a comment

Choose a reason for hiding this comment

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

Approved assuming select/case statement used for gRPC enum.

@github-actions github-actions Bot removed the request for review from timothyb89 October 10, 2022 09:51
@gzdunek gzdunek enabled auto-merge (squash) October 10, 2022 10:52
@gzdunek gzdunek merged commit 2c6a898 into master Oct 10, 2022
@github-actions
Copy link
Copy Markdown
Contributor

@gzdunek See the table below for backport results.

Branch Result
branch/v11 Create PR

@gzdunek gzdunek deleted the gzdunek/add-connect-sftp-support branch October 10, 2022 11:10
@gzdunek gzdunek restored the gzdunek/add-connect-sftp-support branch October 10, 2022 11:10
@gzdunek gzdunek deleted the gzdunek/add-connect-sftp-support branch October 10, 2022 15:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants