用Go语言实现WebSSH远程连接

129

WebSSH远程连接

WebSSH是一种通过Web浏览器远程连接到SSH服务器的技术。它允许用户在不需要本地SSH客户端的情况下,通过Web浏览器连接到远程服务器并执行命令。WebSSH的实现原理是通过WebSocket协议在Web浏览器和SSH服务器之间建立一个双向通信通道,使得用户可以在Web浏览器中输入命令并将其发送到SSH服务器,同时也可以接收SSH服务器的输出并在Web浏览器中显示。

在本文中,我们将使用Go语言的SSH和WebSocket库来实现WebSSH。我们将从创建WebSocket服务器开始,然后创建SSH客户端,创建SSH会话并请求伪终端,设置标准输入和输出管道,最后启动两个goroutine来处理从Web浏览器读取数据和从SSH服务器读取数据的操作。

创建WebSocket服务器

我们首先需要创建一个WebSocket服务器,以便Web浏览器可以连接到它。我们使用Gorilla WebSocket库来创建WebSocket服务器。在main函数中,我们使用http.HandleFunc函数来处理WebSocket连接请求,并使用websocket.Upgrader结构体来升级HTTP连接为WebSocket连接。我们还需要设置CheckOrigin函数,以便允许跨域请求。

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func wsHandle(w http.ResponseWriter, r *http.Request) {
	var (
		conn    *websocket.Conn
		client  *ssh.Client
		sshConn *SSHConnect
		err     error
	)
	if conn, err = upgrader.Upgrade(w, r, nil); err != nil {
		return
	}
	defer conn.Close()

	//Create ssh client
	if client, err = createSSHClient(user, password, host, port); err != nil {
		WsSendText(conn, []byte(err.Error()))
		return
	}
	defer client.Close()

	//connect to ssh
	if sshConn, err = NewSSHConnect(client); err != nil {
		WsSendText(conn, []byte(err.Error()))
		return
	}

	quit := make(chan int)
	go sshConn.Output(conn, quit)
	go sshConn.Recv(conn, quit)
	<-quit
}

func WsSendText(conn *websocket.Conn, b []byte) error {
	if err := conn.WriteMessage(1, b); err != nil {
		return err
	}
	return nil
}

创建SSH客户端

接下来,我们需要创建一个SSH客户端,以便连接到远程SSH服务器。我们使用Go语言的SSH库来创建SSH客户端。在createSSHClient函数中,我们使用用户名和密码进行身份验证,并使用ssh.Dial函数连接到远程SSH服务器。

func createSSHClient(user, password, host string, port int) (*ssh.Client, error) {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		client       *ssh.Client
		//session      *ssh.Session
		err error
	)
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(password))

	clientConfig = &ssh.ClientConfig{
		User: user,
		Auth: auth,
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			//Handling the host key
			return nil
		},
	}
	addr = fmt.Sprintf("%s:%d", host, port)
	if client, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return nil, err
	}
	return client, nil
}

创建SSH会话并请求伪终端

接下来,我们需要创建一个SSH会话,并请求一个伪终端。我们使用client.NewSession函数创建SSH会话,并使用session.RequestPty函数请求伪终端。我们还需要设置终端模式,以便在Web浏览器中正确显示输出。

func NewSSHConnect(client *ssh.Client) (sshConn *SSHConnect, err error) {
    var (
        session *ssh.Session
    )
    if session, err = client.NewSession(); err != nil {
        return
    }
    modes := ssh.TerminalModes{
        ssh.ECHO:          0,
        ssh.TTY_OP_ISPEED: 14400,
        ssh.TTY_OP_OSPEED: 14400,
    }
    if err = session.RequestPty("linux", 80, 40, modes); err != nil {
        return
    }
    // ...
}

设置标准输入和输出管道

接下来,我们需要设置标准输入和输出管道,以便可以在Web浏览器和SSH服务器之间传输数据。我们使用session.StdinPipe函数和session.StdoutPipe函数分别创建标准输入和输出管道,并将它们存储在SSHConnect结构体中。

func NewSSHConnect(client *ssh.Client) (sshConn *SSHConnect, err error) {
    var (
        session *ssh.Session
    )
    if session, err = client.NewSession(); err != nil {
        return
    }
    modes := ssh.TerminalModes{
        ssh.ECHO:          0,
        ssh.TTY_OP_ISPEED: 14400,
        ssh.TTY_OP_OSPEED: 14400,
    }
    if err = session.RequestPty("linux", 80, 40, modes); err != nil {
        return
    }

    pipe, _ := session.StdinPipe()
    stdoutPipe, _ := session.StdoutPipe()

    return &SSHConnect{
        session:    session,
        stdinPipe:  pipe,
        stdoutPipe: stdoutPipe,
    }, nil
}

处理从Web浏览器读取数据和从SSH服务器读取数据的操作

最后,我们需要启动两个goroutine来处理从Web浏览器读取数据和从SSH服务器读取数据的操作。在SSHConnect结构体中,我们定义了Recv函数和Output函数来处理这些操作。Recv函数从WebSocket连接中读取数据,并将其写入SSH服务器的标准输入管道。Output函数从SSH服务器的标准输出管道中读取数据,并将其发送到WebSocket连接。

type SSHConnect struct {
    session    *ssh.Session
    stdinPipe  io.WriteCloser
    stdoutPipe io.Reader
}

func (s *SSHConnect) Recv(conn *websocket.Conn, quit chan int) {
	defer Quit(quit)
	var (
		bytes []byte
		err   error
	)
	for {
		if _, bytes, err = conn.ReadMessage(); err != nil {
			return
		}
		if len(bytes) > 0 {
			if _, e := s.stdinPipe.Write(bytes); e != nil {
				return
			}
		}
	}
}

func (s *SSHConnect) Output(conn *websocket.Conn, quit chan int) {
	defer Quit(quit)
	var (
		read int
		err  error
	)
	tick := time.NewTicker(60 * time.Millisecond)
	defer tick.Stop()
Loop:
	for {
		select {
		case <-tick.C:
			i := make([]byte, 1024)
			if read, err = s.stdoutPipe.Read(i); err != nil {
				fmt.Println(err)
				break Loop
			}
			if err = WsSendText(conn, i[:read]); err != nil {
				fmt.Println(err)
				break Loop
			}
		}
	}
}

func Quit(quit chan int) {
	quit <- 1
}

最后运行ws服务:

func main() {
	http.HandleFunc("/ws/v1", wsHandle)
	http.ListenAndServe(":8080", nil)
}

至此,我们已经完成了WebSSH的实现。用户可以通过Web浏览器连接到WebSocket服务器,并在Web浏览器中输入命令并将其发送到SSH服务器,同时也可以接收SSH服务器的输出并在Web浏览器中显示。WebSSH的实现可以提供一种方便的方式,让用户通过Web浏览器连接到远程SSH服务器并执行命令。它可以减少用户需要安装本地SSH客户端的麻烦,并提供更加友好的用户界面。