1.前言因为公司业务需要在自己的私有云服务器上添加添加WebSsh终端,同时提供输入命令审计功能.
从google上可以了解到xterm.js是一个非常出色的web终端库,包括VSCode很多成熟的产品都使用这个前端库.使用起来也比较简单.
难点是怎么把ssh命令行转换成websocket通讯,来提供Stdin,stdout输出到xterm.js中,接下来就详解技术细节.
全部代码都可以在我的Github.com/dejavuzhou/felix中可以查阅到.
2.知识储备linux下载stdin,stdou和stderr简单概念熟悉Golang官方库golang.org/x/crypto/ssh了解gorilla/websocket的基本用法gin-gonic/gin,当然你也可以使用其他的路由包替代,或者直接使用标准库(前端)websocket(前端)xterm.js3.数据逻辑图Golang堡垒机主要功能就是把SSH协议数据使用websocket协议转发给xterm.js浏览器.
堡垒机Golang服务UML
4.代码实现4.1创建gin Handler func注册gin路由 api.GET("ws/:id", internal.WsSsh)
ssh2ws/internal/ws_ssh.go
package internalimport ( "bytes" "github.com/dejavuzhou/felix/flx" "github.com/dejavuzhou/felix/models" "github.com/dejavuzhou/felix/utils" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "github.com/sirupsen/logrus" "net/http" "strconv" "time")var upGrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024 * 1024 * 10, CheckOrigin: func(r *http.Request) bool { return true },}// handle webSocket connection.// first,we establish a ssh connection to ssh server when a webSocket comes;// then we deliver ssh data via ssh connection between browser and ssh server.// That is, read webSocket data from browser (e.g. 'ls' command) and send data to ssh server via ssh connection;// the other hand, read returned ssh data from ssh server and write back to browser via webSocket API.func WsSsh(c *gin.Context) { v, ok := c.Get("user") if !ok { logrus.Error("jwt token can't find auth user") return } userM, ok := v.(*models.User) if !ok { logrus.Error("context user is not a models.User type obj") return } cols, err := strconv.Atoi(c.DefaultQuery("cols", "120")) if wshandleError(c, err) { return } rows, err := strconv.Atoi(c.DefaultQuery("rows", "32")) if wshandleError(c, err) { return } idx, err := parseParamID(c) if wshandleError(c, err) { return } mc, err := models.MachineFind(idx) if wshandleError(c, err) { return } client, err := flx.NewSshClient(mc) if wshandleError(c, err) { return } defer client.Close() startTime := time.Now() ssConn, err := utils.NewSshConn(cols, rows, client) if wshandleError(c, err) { return } defer ssConn.Close() // after configure, the WebSocket is ok. wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) if wshandleError(c, err) { return } defer wsConn.Close() quitChan := make(chan bool, 3) var logBuff = new(bytes.Buffer) // most messages are ssh output, not webSocket input go ssConn.ReceiveWsMsg(wsConn, logBuff, quitChan) go ssConn.SendComboOutput(wsConn, quitChan) go ssConn.SessionWait(quitChan) <-quitChan //write logs xtermLog := models.TermLog{ EndTime: time.Now(), StartTime: startTime, UserId: userM.ID, Log: logBuff.String(), MachineId: idx, MachineName: mc.Name, MachineIp: mc.Ip, MachineHost: mc.Host, UserName: userM.Username, } err = xtermLog.Create() if wshandleError(c, err) { return } logrus.Info("websocket finished")}代码详解
...