用Go语言实现WebSSH远程连接
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客户端的麻烦,并提供更加友好的用户界面。