WebSSH远程管理Linux服务器、Web终端窗口自适应(二)

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

 

  • 支持用户名密码认证

  • 支持SSH密钥认证

  • 支持Web终端窗口自适应

  • 支持录屏审计

WebSSH远程管理Linux服务器、Web终端窗口自适应(二)

WebSSH远程管理Linux服务器、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代码如下

<template>   <div class="app-container">     <!-- 使用 Element UI 的表单组件创建一个带有标签和输入框的表单 -->     <el-form ref="form" :model="form" :inline="true" label-width="120px">       <el-form-item label="用户名"> <!-- namespace 输入框 -->         <el-input v-model="form.username" />       </el-form-item>       <el-form-item label="密码"> <!-- pod 名称输入框 -->         <el-input v-model="form.password" />       </el-form-item>       <el-form-item label="IP"> <!-- pod 名称输入框 -->         <el-input v-model="form.ip" />       </el-form-item>       <el-form-item label="端口"> <!-- pod 名称输入框 -->         <el-input v-model="form.port" />       </el-form-item>       <el-form-item label="认证类型"> <!-- 容器名称输入框 -->         <el-select v-model="form.auth_type" placeholder="认证类型">           <el-option label="密钥" value="key" />           <el-option label="密码" value="pwd" />         </el-select>       </el-form-item>       <el-form-item label="Command"> <!-- 命令选择框 -->         <el-select v-model="form.command" placeholder="bash">           <el-option label="bash" value="bash" />           <el-option label="sh" value="sh" />         </el-select>       </el-form-item>       <el-form-item> <!-- 提交按钮 -->         <el-button type="primary" @click="onSubmit">SSH</el-button>       </el-form-item>       <div id="terminal" /> <!-- 终端视图容器 -->     </el-form>   </div> </template>  <script> import { Terminal } from 'xterm' // 导入 xterm 包,用于创建和操作终端对象 import { common as xtermTheme } from 'xterm-style' // 导入 xterm 样式主题 import 'xterm/css/xterm.css' // 导入 xterm CSS 样式 import { FitAddon } from 'xterm-addon-fit' // 导入 xterm fit 插件,用于调整终端大小 import { WebLinksAddon } from 'xterm-addon-web-links' // 导入 xterm web-links 插件,可以捕获 URL 并将其转换为可点击链接 import 'xterm/lib/xterm.js' // 导入 xterm 库  export default {   data() {     return {       form: {         username: 'root', // 默认命名空间为 "default"         password: '123', // 默认 shell 命令为 "bash"         command: 'bash', // 默认 shell 命令为 "bash"         auth_type: 'pwd', // 默认容器名称为 "nginx"         ip: '192.168.26.133',         port: 22       }     }   },   methods: {     onSubmit() {       // 创建一个新的 Terminal 对象       const xterm = new Terminal({         theme: xtermTheme,         rendererType: 'canvas',         convertEol: true,         cursorBlink: true       })        // 创建并加载 FitAddon 和 WebLinksAddon       const fitAddon = new FitAddon()       xterm.loadAddon(fitAddon)       xterm.loadAddon(new WebLinksAddon())        // 打开这个终端,并附加到 HTML 元素上       xterm.open(document.getElementById('terminal'))        // 调整终端的大小以适应其父元素       fitAddon.fit()        // 创建一个新的 WebSocket 连接,并通过 URL 参数传递 pod, namespace, container 和 command 信息       const ws = new WebSocket('ws://127.0.0.1:9191/ssh?username=' + this.form.username + '&password=' + this.form.password + '&auth_type=' + this.form.auth_type + '&ip=' + this.form.ip + '&port=' + this.form.port + '&command=' + this.form.command)        // 当 WebSocket 连接打开时,发送一个 resize 消息给服务器,告诉它终端的尺寸       ws.onopen = function() {         ws.send(JSON.stringify({           type: 'resize',           rows: xterm.rows,           cols: xterm.cols         }))       }        // 当从服务器收到消息时,写入终端显示       ws.onmessage = function(evt) {         xterm.write(evt.data)       }        // 当发生错误时,也写入终端显示       ws.onerror = function(evt) {         xterm.write(evt.data)       }        // 当窗口尺寸变化时,重新调整终端的尺寸,并发送一个新的 resize 消息给服务器       window.addEventListener('resize', function() {         fitAddon.fit()         ws.send(JSON.stringify({           type: 'resize',           rows: xterm.rows,           cols: xterm.cols         }))       })        // 当在终端中键入字符时,发送一个 input 消息给服务器       xterm.onData((b) => {         ws.send(JSON.stringify({           type: 'input',           text: b         }))       })     }   } } </script>  <style scoped> .line{   text-align: center; } </style>
  • 在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

  • 选择密钥连接

WebSSH远程管理Linux服务器、Web终端窗口自适应(二)

  • 用户名密码连接

WebSSH远程管理Linux服务器、Web终端窗口自适应(二)

 

发表评论

评论已关闭。

相关文章