-
Notifications
You must be signed in to change notification settings - Fork 894
feat(cli): implement ssh remote forward #8515
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
Changes from 13 commits
cb0f964
a532f7b
6df2a9b
7506df9
7d3d47f
ece557e
9f5e500
0b53a5c
2471dd5
5e87bc6
f4d5c68
9ae8f1d
3e77af3
d8859c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package cli | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"io" | ||
"net" | ||
"regexp" | ||
"strconv" | ||
|
||
gossh "golang.org/x/crypto/ssh" | ||
"golang.org/x/xerrors" | ||
|
||
"github.com/coder/coder/agent/agentssh" | ||
) | ||
|
||
// cookieAddr is a special net.Addr accepted by sshRemoteForward() which includes a | ||
// cookie which is written to the connection before forwarding. | ||
type cookieAddr struct { | ||
net.Addr | ||
cookie []byte | ||
} | ||
|
||
// Format: | ||
// remote_port:local_address:local_port | ||
var remoteForwardRegex = regexp.MustCompile(`^(\d+):(.+):(\d+)$`) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: add a comment to clarify what this should match This could also be done with a simple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
👍
Right, but with regexp we can check if ports are numbers :) |
||
|
||
func validateRemoteForward(flag string) bool { | ||
return remoteForwardRegex.MatchString(flag) | ||
} | ||
mtojek marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
func parseRemoteForward(flag string) (net.Addr, net.Addr, error) { | ||
matches := remoteForwardRegex.FindStringSubmatch(flag) | ||
|
||
remotePort, err := strconv.Atoi(matches[1]) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("remote port is invalid: %w", err) | ||
} | ||
localAddress, err := net.ResolveIPAddr("ip", matches[2]) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("local address is invalid: %w", err) | ||
} | ||
localPort, err := strconv.Atoi(matches[3]) | ||
if err != nil { | ||
return nil, nil, xerrors.Errorf("local port is invalid: %w", err) | ||
} | ||
|
||
localAddr := &net.TCPAddr{ | ||
IP: localAddress.IP, | ||
Port: localPort, | ||
} | ||
|
||
remoteAddr := &net.TCPAddr{ | ||
IP: net.ParseIP("127.0.0.1"), | ||
Port: remotePort, | ||
} | ||
return localAddr, remoteAddr, nil | ||
} | ||
|
||
// sshRemoteForward starts forwarding connections from a remote listener to a | ||
// local address via SSH in a goroutine. | ||
// | ||
// Accepts a `cookieAddr` as the local address. | ||
func sshRemoteForward(ctx context.Context, stderr io.Writer, sshClient *gossh.Client, localAddr, remoteAddr net.Addr) (io.Closer, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for reviewers: moved from |
||
listener, err := sshClient.Listen(remoteAddr.Network(), remoteAddr.String()) | ||
if err != nil { | ||
return nil, xerrors.Errorf("listen on remote SSH address %s: %w", remoteAddr.String(), err) | ||
} | ||
|
||
go func() { | ||
for { | ||
remoteConn, err := listener.Accept() | ||
if err != nil { | ||
if ctx.Err() == nil { | ||
_, _ = fmt.Fprintf(stderr, "Accept SSH listener connection: %+v\n", err) | ||
} | ||
return | ||
} | ||
|
||
go func() { | ||
defer remoteConn.Close() | ||
|
||
localConn, err := net.Dial(localAddr.Network(), localAddr.String()) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(stderr, "Dial local address %s: %+v\n", localAddr.String(), err) | ||
return | ||
} | ||
defer localConn.Close() | ||
|
||
if c, ok := localAddr.(cookieAddr); ok { | ||
_, err = localConn.Write(c.cookie) | ||
if err != nil { | ||
_, _ = fmt.Fprintf(stderr, "Write cookie to local connection: %+v\n", err) | ||
return | ||
} | ||
} | ||
|
||
agentssh.Bicopy(ctx, localConn, remoteConn) | ||
}() | ||
} | ||
}() | ||
|
||
return listener, nil | ||
} |
Uh oh!
There was an error while loading. Please reload this page.