WebSSH远程管理Linux服务器、Web终端窗口自适应(二)
阅读原文时间:2023年07月18日阅读:1

上一篇:Gin+Xterm.js实现WebSSH远程Kubernetes Pod

  • 支持用户名密码认证

  • 支持SSH密钥认证

  • 支持Web终端窗口自适应

  • 支持录屏审计

Go SSH

golang.org/x/crypto/ssh 是 Go 语言的一个库,它提供了 SSH(Secure Shell)协议的实现,可以用来构建 SSH 客户端和服务器。

  • 安装

    go get golang.org/x/crypto/ssh

  • SSH基本示例

在创建SSH客户端之前,首先需要创建一个ClientConfig对象,其中包含了进行SSH通信所必须的配置信息。

config := &ssh.ClientConfig{
    User: "username",
    Auth: []ssh.AuthMethod{
        ssh.Password("password"),
    },
    HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}

在上述代码中,我们设置了用户名(User)、认证方式(Auth)和主机密钥回调(HostKeyCallback)。请注意,为了安全起见,在生产环境中不应使用InsecureIgnoreHostKey,而应使用更严格的主机密钥检查方式。

然后可以使用 ssh.Dial 函数来创建一个 SSH 客户端连接:

client, err := ssh.Dial("tcp", "localhost:22", config)
if err != nil {
    log.Fatal("Failed to dial: ", err)
}

创建了 SSH 客户端连接之后,我们就可以使用它来执行远程命令。例如:

session, err := client.NewSession()
if err != nil {
    log.Fatal("Failed to create session: ", err)
}
defer session.Close()

out, err := session.CombinedOutput("ls")
if err != nil {
    log.Fatal("Failed to run command: ", err)
}

fmt.Println(string(out))

在这里,我们首先使用 client.NewSession 方法创建了一个新的 SSH 会话。然后,我们使用 session.CombinedOutput 方法来执行远程命令并获取其输出。

使用Gin、x/crypto/ssh 实现SSH

package main

import (
 "encoding/json"
 "fmt"
 "github.com/gin-gonic/gin"
 "github.com/gorilla/websocket"
 "golang.org/x/crypto/ssh"
 "log"
 "net/http"
 "os"
)

const (
 // 输入消息
 messageTypeInput = "input"
 // 调整窗口大小消息
 messageTypeResize = "resize"
 // 密钥认证方式
 authTypeKey = "key"
 // 密码认证方式
 authTypePwd = "pwd"
)

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

// WSClient WebSocket客户端访问对象,包含WebSocket连接对象和SSH会话对象
type WSClient struct {
 // WebSocket 连接对象
 ws         *websocket.Conn
 sshSession *ssh.Session
}

// Message 用于解析从websocket接收到的json消息
type Message struct {
 Type string `json:"type"`
 Cols int    `json:"cols"`
 Rows int    `json:"rows"`
 Text string `json:"text"`
}

// WSClient 的 Read 方法,实现了 io.Reader 接口,从 websocket 中读取数据。
func (c *WSClient) Read(p []byte) (n int, err error) {
 // 从 WebSocket 中读取消息
 _, message, err := c.ws.ReadMessage()
 if err != nil {
  return 0, err
 }
 msg := &Message{}
 if err := json.Unmarshal(message, msg); err != nil {
  return 0, err
 }

 switch msg.Type {
 case messageTypeInput:
  // 如果是输入消息
  return copy(p, msg.Text), err
 case messageTypeResize:
  // 如果是窗口调整消息、调整窗口大小
  return 0, c.WindowChange(msg.Rows, msg.Cols)
 default:
  return 0, fmt.Errorf("invalid message type")
 }
}

// WindowChange 改变SSH Session窗口大小
func (c *WSClient) WindowChange(rows, cols int) error {
 return c.sshSession.WindowChange(rows, cols)
}

// WSClient 的 Write 方法,实现了 io.Writer 接口,将数据写入 websocket。
func (c *WSClient) Write(p []byte) (n int, err error) {
 // 将数据作为文本消息写入 WebSocket
 err = c.ws.WriteMessage(websocket.TextMessage, p)
 return len(p), err
}

// 建立SSH Client
func sshDial(user, password, ip, authType string, port int) (*ssh.Client, error) {
 var authMethods []ssh.AuthMethod
 // 根据认证类型选择密钥或密码认证
 switch authType {
 case authTypeKey:
  privateKeyByte, err := os.ReadFile("./id_rsa")
  if err != nil {
   return nil, err
  }
  privateKey, err := ssh.ParsePrivateKey(privateKeyByte)
  if err != nil {
   return nil, err
  }
  authMethods = append(authMethods, ssh.PublicKeys(privateKey))

 case authTypePwd:
  authMethods = append(authMethods, ssh.Password(password))
 }
 // SSH client配置
 config := &ssh.ClientConfig{
  User:            user,
  Auth:            authMethods,
  HostKeyCallback: ssh.InsecureIgnoreHostKey(),
 }
 // 创建SSH client
 return ssh.Dial("tcp", fmt.Sprintf("%s:%d", ip, port), config)
}

// SSHHandler 处理SSH会话
func SSHHandler(wsClient *WSClient, user, password, ip, authType, command string, port int) {
 // 创建SSH client
 sshClient, err := sshDial(user, password, ip, authType, port)
 if err != nil {
  log.Fatal(err)
 }
 defer sshClient.Close()

 // 创建SSH session
 session, err := sshClient.NewSession()
 if err != nil {
  log.Fatal(err)
 }
 defer session.Close()

 wsClient.sshSession = session
 // 设置终端类型及大小
 terminalModes := ssh.TerminalModes{
  ssh.ECHO:          1,
  ssh.TTY_OP_ISPEED: 14400,
  ssh.TTY_OP_OSPEED: 14400,
 }
 if err := session.RequestPty("xterm", 24, 80, terminalModes); err != nil {
  log.Fatal(err)
 }
 // 关联对应输入、输出流
 session.Stderr = wsClient
 session.Stdout = wsClient
 session.Stdin = wsClient
 // 在远程执行命令
 if err := session.Run(command); err != nil {
  log.Fatal(err)
 }

}

// Query 查询参数
type Query struct {
 UserName string `form:"username" binding:"required"`
 Password string `form:"password"`
 IP       string `form:"ip" binding:"required"`
 Port     int    `form:"port" binding:"required"`
 AuthType string `form:"auth_type" binding:"required,oneof=key pwd"`
 Command  string `form:"command" binding:"required,oneof=sh bash"`
}

func main() {
 router := gin.Default()
 router.GET("/ssh", func(ctx *gin.Context) {
  var r Query
  // 绑定并校验请求参数
  if err := ctx.ShouldBindQuery(&r); err != nil {
   ctx.JSON(http.StatusBadRequest, gin.H{
    "err": err.Error(),
   })
   return
  }
  // 将 HTTP 连接升级为 websocket 连接
  ws, err := upgrader.Upgrade(ctx.Writer, ctx.Request, nil)
  if err != nil {
   log.Printf("Failed to upgrade connection: %v", err)
   return
  }
  // 开始处理 SSH 会话
  SSHHandler(&WSClient{
   ws: ws,
  }, r.UserName, r.Password, r.IP, r.AuthType, r.Command, r.Port,
  )
 })

 router.Run(":9191")
}

后端项目完整代码:https://gitee.com/KubeSec/webssh/tree/master/go-ssh

使用vue-admin-template和Xterm.js实现Web终端

https://github.com/PanJiaChen/vue-admin-template

https://github.com/xtermjs/xterm.js

  • 下载vue-admin-template项目

https://github.com/PanJiaChen/vue-admin-template.git

  • 安装xterm.js及插件

    npm install
    npm install xterm
    npm install --save xterm-addon-web-links
    npm install --save xterm-addon-fit
    npm install -S xterm-style

  • 打开vue-admin-template项目,在src/views目录下新建目录ssh,在ssh目录下新建index.vue代码如下

  • 在src/router/index.js文件中增加路由

    {
    path: '/ssh',
    component: Layout,
    children: [
    {
    path: 'ssh',
    name: 'SSH',
    component: () => import('@/views/ssh/index'),
    meta: { title: 'SSH', icon: 'form' }
    }
    ]
    },

  • 启动项目

    npm install
    npm run dev

  • 前端全部代码

https://gitee.com/KubeSec/webssh/tree/master/webssh

测试

  • 生成SSH密码

    ssh-keygen -t rsa
    cd /root/.ssh/
    cp id_rsa.pub authorized_keys

访问http://localhost:9528/#/ssh/ssh

  • 选择密钥连接

  • 用户名密码连接

手机扫一扫

移动阅读更方便

阿里云服务器
腾讯云服务器
七牛云服务器

你可能感兴趣的文章