Skip to content

Add web terminal handlers for file transfer requests#24581

Merged
avatus merged 30 commits intomichaelmyers/ssh_server_file_transfer_handlersfrom
michaelmyers/web_terminal_file_request_handlers
May 3, 2023
Merged

Add web terminal handlers for file transfer requests#24581
avatus merged 30 commits intomichaelmyers/ssh_server_file_transfer_handlersfrom
michaelmyers/web_terminal_file_request_handlers

Conversation

@avatus
Copy link
Copy Markdown
Contributor

@avatus avatus commented Apr 13, 2023

Part of #23546
Relies on #24580

web buddy: #24583

This PR adds the fileTransferC channel to our web terminal to listen to websocket events dealing with file transfer requests. The two events added are for requesting a new file transfer, and responding to an existing file transfer.

Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread api/observability/tracing/ssh/ssh.go Outdated
Comment thread lib/web/terminal.go Outdated
Comment thread lib/srv/termhandlers.go Outdated
if err != nil {
return trace.Wrap(err)
}
t.SessionRegistry.NotifyFileTransferRequest(req, FileTransferApproved, scx)
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.

why this notify is missing?

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.

This is just some base branch shenanigans. Both of the Notifys happen inside the approve/deny methods now but it'll be updated here when I get the base branch updated/merged. Ignore for now!

Comment thread lib/web/terminal.go
if err != nil {
return 0, trace.Wrap(err)
}
approved, ok := e["approved"].(bool)
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.

i haven't seen that part in RFD. Some audit event will be extended to have this field or does it already have it? can you link it?

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.

This approved is handling the file transfer response. Is the response "Approved" or "Denied" basically.

Comment thread lib/web/terminal.go Outdated
}

return 0, nil
case defaults.WebsocketFileTransferRequestResponse:
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.

i saw that in resizeC we check for nil channel at top, do we need it here as well?

Comment thread lib/web/terminal.go Outdated
select {
case <-t.terminalContext.Done():
return
case transferRequest := <-fileTransferC:
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.

Don't we have to handle the channel getting closed, just as handleWindowResize does?

case params := <-resizeC:
// nil params indicates the channel was closed
if params == nil {
return
}

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.

Shouldn't the new version of handleFileTransfer account for individual channels getting closed?

Comment thread lib/web/terminal.go Outdated
return
case transferRequest := <-fileTransferC:
// if not response, this is a new file transfer request
if !transferRequest.Response {
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.

Did you consider representing those two different request types as two separate structs or something akin to a discriminated union in TypeScript?

Packing every possible field in a single type and then differentiating between them based on the assumed shape doesn't sound like a sound long-term strategy to me. I can't help here much unfortunately without doing a deeper dive into the code. I'd ask someone who knows Go better than me if it'd be possible to represent those two different types of requests as different structs.

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.

Yeah, this grew from "eh only 1 field is needed" to a few, and I didn't cut them apart. It's best to do that now and make it more clear.

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.

After splitting the structs, I made a new chan, one to handle the requests, and one to handle the responses. The original reason to have this struct be the same was to reuse the same chan and just make decisions based on what info came through but I think thats a bit lazy now. Although I have a separate channel, I kept the "setting" of the channels in the same method (just with the two channels now instead of one) and also, have both cases in the same handler as well. Came out cleaner and easier to parse around throughout the app. Thanks for keeping me accountable

EnvsJSON []byte `json:"envs"`
}

type FileTransferReq struct {
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.

Looking at other definitions in this file, shouldn't these two have a godoc as well?

func (s *Session) FileTransferRequestResponse(ctx context.Context, req FileTransferResponseReq) error {
const request = "file-transfer-request-response"
config := tracing.NewConfig(s.wrapper.opts)
ctx, span := config.TracerProvider.Tracer(instrumentationName).Start(
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.

Do we need to set up all of this tracing stuff even though in the end we end up just calling s.Session.SendRequest which does that again? I don't really know much about how we do tracing, I'm looking at SetEnv which ends up calling SendRequest too, but SetEnv actually does some extra stuff.

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.

s.Session.SendRequest doesn't have the tracing as that uses the wrapped ssh.Session, but if I instead just used s.SendRequest, then yeah, I can ignore the tracing as that SendRequest does set it up. Good call

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.

Ah, I didn't notice the difference between the two. Still, I'd recommend asking on #dev-teleport if one way of doing this is preferred over the other.

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.

If you want any custom attributes to be included in the span you will need to create one explicitly. The underlying ssh request tracing will simply tell you that a request named foo was sent.

@avatus avatus force-pushed the michaelmyers/web_terminal_file_request_handlers branch from 959073c to 8521b0d Compare April 16, 2023 21:31
@avatus
Copy link
Copy Markdown
Contributor Author

avatus commented Apr 16, 2023

Didn't mean to rebase this early and didn't realize after I pushed, sorry!

@ravicious ravicious self-requested a review April 17, 2023 11:38
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.

Some minor adjustments are needed but looks ok otherwise.

Comment thread lib/web/terminal.go Outdated
Comment on lines +1242 to +1244
download, ok := e["download"].(bool)
if !ok {
return 0, trace.BadParameter("Unable to find approved status on response")
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.

The error message doesn't match the check that's being performed here.

Comment thread lib/defaults/defaults.go Outdated

// WebsocketFileTransferRequestResponse is received when a response (approve/deny) has been
// made for an existing file transfer request
WebsocketFileTransferRequestResponse = "t"
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.

It'd be nice to add some tests for that based on TestResizeTerminal.

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.

+1

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.

@avatus can we add some test coverage for this?

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 👍


// FileTransferRequestApprove sends a "file-transfer-request-response" ssh request
// The response will contain an Approve bool which will approve or deny a requested file transfer
func (s *Session) FileTransferRequestResponse(ctx context.Context, req FileTransferResponseReq) 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.

When I was reviewing the web PR, I realized that this might need a better name. The other method names are verb based while this one feels a bit odd compared to them.

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 tried thinking of something that would make sense for both like ResponseToFileTransferRequest but, idk I didn't like it. So I just split them into to, ApproveFileTransferRequest and DenyFileTransferRequest. They both take just the requestID now as a string, and the req is created inside the method and sets Approved appropriately. Maybe a bit verbose but much more readable and understandable

Comment thread api/observability/tracing/ssh/session.go Outdated
func (s *Session) FileTransferRequestResponse(ctx context.Context, req FileTransferResponseReq) error {
const request = "file-transfer-request-response"
config := tracing.NewConfig(s.wrapper.opts)
ctx, span := config.TracerProvider.Tracer(instrumentationName).Start(
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.

If you want any custom attributes to be included in the span you will need to create one explicitly. The underlying ssh request tracing will simply tell you that a request named foo was sent.

Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread api/observability/tracing/ssh/session.go Outdated
Comment thread lib/web/terminal.go Outdated
Comment thread lib/web/terminal.go Outdated
Comment thread lib/defaults/defaults.go Outdated

// WebsocketFileTransferRequestResponse is received when a response (approve/deny) has been
// made for an existing file transfer request
WebsocketFileTransferRequestResponse = "t"
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.

+1

@avatus avatus requested a review from rosstimothy May 1, 2023 15:49
Comment thread lib/sshutils/sftp/http.go Outdated
avatus added 4 commits May 3, 2023 14:17
…chaelmyers/web_terminal_file_request_handlers
* Add file transfer handlers to web terminal

* remove unused keys

* Add FileTransferContainer

This PR separates the absolute positioned container from the FileTransfer component. We will be adding FileTransferRequests
in the same spot, and this will allow them to be visible together without css shenanigans trying to absolutely place them together.
FileTransferRequests are separate from FileTransfers in that they interact with the tty.

* Add useFileTransfer hook

* Use useFileTransfer hook inside useSshSession

* Add tty handlers

* Update getScpUrl to add optional moderated params

* Use FileTransferContainer in Connect

* Update DocumentSSH

* remove fmt

* remove unused shellcmd key

* lint fixes

* remove pending file after get

* remove pending file after get

* Clean up console log

* Update filetransfererquest to use download bool

* Fix upload/download flow

* Fix typecheck and FileTransfer tests

* Fix UI display bugs and add license

* Change filetransferresponse to filetransferdecision

* Refactor useFileTransfer up to DocumentSsh

* Add license

* Fix lint error

* update pending transfer text

* use handleUpload/handleDownload, move FileTransferRequests to prop

* Review feedback

* Add usecallback to scpurls and useattempt

* Apply patch

* Update optional params
@avatus avatus merged this pull request into michaelmyers/ssh_server_file_transfer_handlers May 3, 2023
@avatus avatus deleted the michaelmyers/web_terminal_file_request_handlers branch May 3, 2023 20:14
avatus added a commit that referenced this pull request May 3, 2023
Add web terminal handlers for file transfer requests (#24581)
avatus added a commit that referenced this pull request May 3, 2023
Add web terminal handlers for file transfer requests (#24581)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants