Golang:通过 WebSocket 转发 SSH

huangapple go评论92阅读模式
英文:

Golang: forward ssh over websocket

问题

我能够使用golang将telnet通过websocket转发,类似这样的方式:

func forwardtcp(wsconn *websocket.Conn, conn *telnet.Conn) {
    connbuf := bufio.NewReader(conn)
    tcpbuffer := make([]byte, 128)

    for {
        n, err := connbuf.Read(tcpbuffer)

        if err != nil {
            log.Println("TCP Read failed")
            break
        }
        if err == nil {
            wsconn.WriteMessage(websocket.BinaryMessage, tcpbuffer[:n])
        }
    }
}

然而,我无法使用SSH或shell会话做类似的操作。我对使用以下代码中的targetStdout, _ := session.StdoutPipe()targetStdin, _ := session.StdinPipe()这两个部分的基本概念不太理解。

我可以使用io.Copy,但不确定如何将它们格式化为可以通过websocket连接发送的数据包。

是否可以以一种方式处理targetStdintargetStdout管道,使其可以读取和写入字节,例如从websocket连接接收到的字节?
或者是否有更好的方法来获取SSH连接的io?

英文:

I've been able to forward telnet over a websocket using golang, using something like

func forwardtcp(wsconn *websocket.Conn, conn *telnet.Conn) {
	connbuf := bufio.NewReader(conn)
	tcpbuffer := make([]byte, 128)

	for {
		n, err := connbuf.Read(tcpbuffer)

		if err != nil {
			log.Println("TCP Read failed")
			break
		}
		if err == nil {
			wsconn.WriteMessage(websocket.BinaryMessage, tcpbuffer[:n])
		}
	}
}

However I'm unable to do similar with an SSH or shell session. I'm not understanding a fundamental concept with the using the

targetStdout, _ := session.StdoutPipe()
targetStdin, _ := session.StdinPipe()

pieces.

I am able to use io.Copy, but not sure how to format these into a datagram that can be sent with the websocket connection.

Is it possible to treat the targetStdin and targetStdout pipes in a manner that they can be read and written to with bytes, such as those received from the websocket connection?
Or is there a better approach to get io from the SSH connection?

答案1

得分: 1

如果您想要使用SSH进行远程shell会话,您应该使用golang.org/x/crypto/ssh包,而不是websocket包。在godoc.org上有一个很好的示例,我在这里重复一下:

// SSH客户端通过ClientConn表示。目前只支持“password”身份验证方法。
//
// 要使用远程服务器进行身份验证,您必须通过ClientConfig的Auth字段传递至少一个AuthMethod的实现。
config := &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{
        ssh.Password("yourpassword"),
    },
}
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
if err != nil {
    panic("Failed to dial: " + err.Error())
}

// 每个ClientConn可以支持多个交互式会话,由Session表示。
session, err := client.NewSession()
if err != nil {
    panic("Failed to create session: " + err.Error())
}
defer session.Close()

// 创建Session后,您可以使用Run方法在远程端执行单个命令。
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
    panic("Failed to run: " + err.Error())
}
fmt.Println(b.String())

您可能希望使用ssh.Session结构体的字段,而不是使用StdoutPipe()

type Session struct {
    Stdin  io.Reader
    Stdout io.Writer
    Stderr io.Writer
}

示例中的session.Stdout = &b表示远程进程的标准输出将被写入b。同样,您可以将任何io.Reader分配给session.Stdin,远程进程将将其作为标准输入读取。有关ssh.Session的详细行为,请参阅godoc.org

英文:

If you want to use SSH for a remote shell session you should not use the websocket package but the golang.org/x/crypto/ssh package. There is an excellent example at godoc.org which I repeat here:

// An SSH client is represented with a ClientConn. Currently only
// the "password" authentication method is supported.
//
// To authenticate with the remote server you must pass at least one
// implementation of AuthMethod via the Auth field in ClientConfig.
config := &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{
        ssh.Password("yourpassword"),
    },
}
client, err := ssh.Dial("tcp", "yourserver.com:22", config)
if err != nil {
    panic("Failed to dial: " + err.Error())
}

// Each ClientConn can support multiple interactive sessions,
// represented by a Session.
session, err := client.NewSession()
if err != nil {
    panic("Failed to create session: " + err.Error())
}
defer session.Close()

// Once a Session is created, you can execute a single command on
// the remote side using the Run method.
var b bytes.Buffer
session.Stdout = &b
if err := session.Run("/usr/bin/whoami"); err != nil {
    panic("Failed to run: " + err.Error())
}
fmt.Println(b.String())

You probably want to use the fields of the ssh.Session struct instead of using StdoutPipe().

type Session struct {
    Stdin io.Reader
    Stdout io.Writer
    Stderr io.Writer
}

The line in the example session.Stdout = &b means that the stdout from the remote process will be written to b. Likewise, you can assign any io.Reader to session.Stdin which will be read as stdin by the remote process. The detailed behavior of ssh.Session can be found at godoc.org

答案2

得分: 1

一个websocket.Conn是一个ReadWriter,因此它可以作为io.Copy的源和目标。Cmd.StdoutPipeSession.StdoutPipe都是io.Reader,而相应的Stdin版本是io.Writer。所以一切都应该很好地连接在一起。你只需要在两个方向上进行复制。

go io.Copy(targetStdin, wsconn)
io.Copy(wsconn, targetStdout)
英文:

A websocket.Conn is a ReadWriter, so it can be both the source and destination for io.Copy. Both Cmd.StdoutPipe and Session.StdoutPipe are io.Reader and the Stdin versions are io.Writer. So everything should glue together just fine. You just have to copy in both directions.

go io.Copy(targetStdin, wsconn)
io.Copy(wsconn, targetStdout)

huangapple
  • 本文由 发表于 2015年5月26日 16:06:02
  • 转载请务必保留本文链接:https://go.coder-hub.com/30452901.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定