概述
使用xterm.js+socket技术实现一个网页端的webShell功能,完成shell连接操作,并记录用户的操作日志。
插件版本
这里使用的xtrem及相关插件版本如下:
"xterm": "^5.1.0", "xterm-addon-fit": "^0.7.0"
后端使用了https://github.com/mojocn/felix.git
中封装好的socket操作请求
go get github.com/libragen/felix
前端采用的是Vue+Xtrem.js
后端使用golang(基于gin框架)
前端代码
<template>
<div id="xterm" class="terminal" />
</template>
<script>
import 'xterm/css/xterm.css'
import { Terminal } from 'xterm'
import { FitAddon } from 'xterm-addon-fit'
import { Base64 } from 'js-base64'
export default{
name: 'Shell',
data() {
return {
socket: null,
term: null,
hostInfo: {
ip: '',
port: '',
pass: ''
}
}
},
mounted(){
// 这里执行获取要ssh连接的服务器信息的逻辑
this.hostInfo.ip = '192.168.1.1'
this.hostInfo.port = '22'
this.hostInfo.pass = '123456'
this.initTerm()
},
beforeDestroy(){
if (this.socket !== null) {
this.socket.close()
}
if (this.term !== null) {
this.term.dispose()
}
},
methods: {
initTerm() {
const term = new Terminal({
frontSize: 14,
lineHeight: 1.2,
cursorBlink: false,
cursorStyle: 'block', // 光标样式 null | 'block' | 'underline' | 'bar'
scrollback: 800, // 回滚
tabStopWidth: 8, // 制表宽度
screenKeys: true
})
const fitAddon = new FitAddon()
term.loadAddon(fitAddon)
term.open(document.getElementById('xterm'))
fitAddon.fit()
this.cols = term.
term.focus()
this.term.onData(function(data) {
g.socket.send(
JSON.stringify({
type: 'cmd',
cmd: Base64.encode(data)
})
)
})
},
initSocket() {
this.socket = new WebSocket('ws://localhost:8888/shell/' + this.hostInfo.ip + '/' + this.hostInfo.port + '/' + this.hostInfo.pass + '/' + this.term.cols + '/' + this.term.rows)
this.socket.onopen = () => {
//这边要用\r而不是\n,\n会导致下一行有空格
this.term.write('连接已建立\r')
}
this.socket.onclose = () => {
this.term.write('连接已断开\n')
}
this.socket.onmessage = (ev) => {
this.term.write(ev.data)
}
}
}
}
</script>
<style scoped>
.terminal {
width: 100%;
height: 100%;
font-family: 'Courier New', Courier, monospace;
overflow: hidden;
}
</style>
后端代码
接收请求
func Routers(r *gin.RouterGroup) {
// socket请求到后端默认是GET类型的,这边需要使用GET来接收
r.GET("/shell/:ip/:port/:pass/:cols/:rows", Shell)
}
处理连接的逻辑(shell.go)
import (
"github.com/libragen/felix/util"
"strconv"
)
func Shell(c *gin.Context){
var (
row int
col int
err errors
)
ip := c.Param("ip")
port := c.Param("port")
pass := c.Param("pass")
rowStr := c.Param("rows")
colStr := c.Param("cols")
rows, err = strconv.Atoi(rowStr)
cols, err = strconv.Atoi(colStr)
startTime := time.Now()
// 创建ssh连接客户端
conn := ssh.NewSSHClientByPass(user, pass, ip, port)
// 建立ssh连接
err = conn.ConnectByPass()
ssConn, err := util.NewSshConn(cols, rows, conn.GetClient())
defer ssConn.Close()
wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
defer wsConn.Close()
quitChan := make(chan bool, 3)
var logBuff = new(bytes.Buffer)
go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan)
go ssConn.SendComboOutput(wsConn, quitChan)
go ssConn.SessionWait(quitChan)
<-quitChan
//写入操作日志(这里就是基于gorm的写数据库操作了,具体的字段就不列出来了,可以根据自己的需要进行修改)
var shellLog ShellLog
jsons := make(map[string]interface{}, 1)
shellLog.ShellType = "主机连接(密码模式)"
shellLog.ConnIp = ip
shellLog.ConnUser = user
shellLog.ConnEndTime = time.Now()
shellLog.ConnStartTime = startTime
jsons["command"] = logBuff.Bytes()
newJson, _ := json.Marshal(jsons)
shellLog.Cmd = newJson
if logBuff.Bytes() != nil {
_, errs := shellLog.Insert()
if errs != nil {
log.Errorf("写入shell日志时发生异常:%v", errs)
}
}
}
客户端连接的代码(ssh.go)
import (
"github.com/pkg/sftp"
"github.com/spf13/viper"
"golang.org/x/crypto/ssh"
)
type Client struct {
user string
pwd string
ip string
port string
sshClient *ssh.Client
sftpClient *sftp.Client
}
func NewSSHClientByPass(user, pwd, ip, port string) Client {
return Client{
user: user,
pwd: pwd,
ip: ip,
port: port,
}
}
func (c *Client) ConnectByPass error {
config := c.getConfigByPass()
client, err := ssh.Dial("tcp", c.ip+":"+c.port, config)
if err != nil {
return fmt.Errorf("创建ssh连接失败: %w", err)
}
sftp, err := sftp.NewClient(client)
if err != nil {
return fmt.Errorf("创建sftp连接失败: %w", err)
}
c.sshClient = client
c.sftpClient = sftp
return nil
}
func (c *Client) getConfigByPass() *ssh.ClientConfig {
config := &ssh.ClientConfig{
User: c.user,
Auth: []ssh.AuthMethod{
ssh.Password(c.pwd),
},
Timeout: 30 * time.Second,
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
return config
}
后端ssh与xterm的交互具体的逻辑操作可以参考这篇文章,里面有详细的解读
https://mojotv.cn/2019/05/27/xtermjs-go