首页 > 其他分享 >基于golang实现ssh terminal

基于golang实现ssh terminal

时间:2023-04-24 12:33:15浏览次数:42  
标签:terminal return err nil golang session ws ssh

基于golang实现ssh terminal

实现ssh terminal相对比较容易,简单来说需要初始化ssh连接后,通过ssh连接创建一个会话,定义好输入、输出,然后再请求pty(需要定义好modes)与远程会话进行关联。

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh"
	"io"
	"log"
	"net"
	"net/http"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"
)

type loginInfo struct {
	Address  string `json:"ipaddress"`
	Port     int    `json:"port"`
	UserName string `json:"username"`
	PassWord string `json:"password"`
}

//将字符串转换为int
func toInt(str string) int {
	data, err := strconv.Atoi(str)
	if err != nil {
		fmt.Println(err)
	}
	return data
}

//初始化ws,将http协议提升为ws协议
func InitialWS(c *gin.Context) (*websocket.Conn, error) {
	var upgrader = websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
	}
	//将http协议提升为ws
	ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		fmt.Println(err)
		return nil, err
	}
	return ws, err
}

//实现write方法,保存ws连接
type WSoutput struct {
	ws *websocket.Conn
}

// Write: implement Write interface to write bytes from ssh server into bytes.Buffer.
func (w *WSoutput) Write(p []byte) (int, error) {
	// 处理非utf8字符
	if !utf8.Valid(p) {
		bufStr := string(p)
		buf := make([]rune, 0, len(bufStr))
		for _, r := range bufStr {
			if r == utf8.RuneError {
				buf = append(buf, []rune("@")...)
			} else {
				buf = append(buf, r)
			}
		}
		p = []byte(string(buf))
	}
	err := w.ws.WriteMessage(websocket.TextMessage, p)
	return len(p), err
}

type sshConnect struct {
	connect   *ssh.Client    //ssh连接
	session   *ssh.Session   //ssh会话
	stdinPipe io.WriteCloser //标准输入管道
}

// 初始化ssh连接
func (t *sshConnect) InitialSSH(login loginInfo) error {
	//初始化ssh登陆配置
	config := &ssh.ClientConfig{
		User:    login.UserName,
		Auth:    []ssh.AuthMethod{ssh.Password(login.PassWord)},
		Timeout: 5 * time.Second,
		Config: ssh.Config{
			Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "[email protected]", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
		},
		HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
			return nil
		},
	}
	//创建ssh连接
	sshConnect, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", login.Address, login.Port), config)
	if err != nil {
		return err
	}
	t.connect = sshConnect
	return nil
}

//初始化终端
func (t *sshConnect) InitialTerminal(ws *websocket.Conn, rows, cols int) error {
	session, err := t.connect.NewSession()
	if err != nil {
		log.Println(err)
		return err
	}
	t.session = session
	t.stdinPipe, _ = session.StdinPipe()

	wsOutput := WSoutput{
		ws: ws,
	}
	//ssh.stdout and stderr will write output into comboWriter
	session.Stdout = &wsOutput
	session.Stderr = &wsOutput

	//定义terminal模式
	modes := ssh.TerminalModes{
		ssh.ECHO:          1, //开启回显
		ssh.TTY_OP_ISPEED: 14400,
		ssh.TTY_OP_OSPEED: 14400,
	}

	//将pty与远程的会话关联起来
	if err := session.RequestPty("xterm", rows, cols, modes); err != nil {
		return err
	}
	// Shell starts a login shell on the remote host
	if err := session.Shell(); err != nil {
		return err
	}
	return nil
}

//处理连接
func (t *sshConnect) Connect(ws *websocket.Conn) {
	stopCh := make(chan struct{}, 1)

	//启用协程获取webTerminal的输入
	go func() {
		for {
			// 读取web socket信息,msg为用户输入的信息
			_, msg, err := ws.ReadMessage()
			if err != nil {
				log.Println(err)
				close(stopCh)
			}
			//心跳检测
			if string(msg) == "ping" {
				continue
			}
			//terminal窗口调整
			if strings.Contains(string(msg), "resize") {
				resizeSlice := strings.Split(string(msg), ":")
				rows, _ := strconv.Atoi(resizeSlice[1])
				cols, _ := strconv.Atoi(resizeSlice[2])
				err := t.session.WindowChange(rows, cols)
				if err != nil {
					log.Println(err)
					close(stopCh)
				}
				continue
			}
			_, err = t.stdinPipe.Write(msg)
			if err != nil {
				close(stopCh)
				return
			}
		}
	}()

	timer := time.NewTimer(time.Minute * 30)

	defer func() {
		ws.Close()
		t.stdinPipe.Close()
		t.session.Close()
		t.connect.Close()
		if err := recover(); err != nil {
			log.Println(err)
		}
	}()

	for {
		select {
		case <-stopCh:
			return
		case <-timer.C:
			return
		}
	}
}

func sshTerminal(c *gin.Context) {
	host := c.DefaultQuery("host", "")
	cols := c.DefaultQuery("cols", "150")
	rows := c.DefaultQuery("rows", "35")

	sshInfo := loginInfo{
		Address:  host,
		Port:     22,
		UserName: "root",
		PassWord: "password",
	}

	//将http协议提升为ws协议
	ws, err := InitialWS(c)
	if err != nil {
		fmt.Println(err)
		return
	}
	connectAction := sshConnect{}
	err = connectAction.InitialSSH(sshInfo)
	err = connectAction.InitialTerminal(ws, toInt(rows), toInt(cols))
	if err != nil {
		ws.WriteMessage(1, []byte(err.Error()))
		ws.Close()
		log.Println(err)
		return
	}
	connectAction.Connect(ws)
}

func main() {
	r := gin.Default()
	r.POST("/terminal", sshTerminal)
	r.Run(":9090")
}

标签:terminal,return,err,nil,golang,session,ws,ssh
From: https://www.cnblogs.com/duyixu/p/17349059.html

相关文章

  • Google Cloud磁盘空间占满导致无法ssh进入的解决方案
    GoogleCloud磁盘空间沾满导致ssh无法进入的解决方案我他妈真的吐了,昨天在写apriori算法的时候,chatgpt弄了半天都不对,然后我重新看了下这个算法,发现其实也不难,就暴力写了个。因为python的类型太过自由,一直以2行/分钟的速度在推进度(用C++早tm写完了呀),然后终于跑出来了一些像样的......
  • golang中sync.Pool的使用示例
    先上代码:packagemainimport( "fmt" "sync")varpoolsync.Pooltypepersonstruct{ Namestring Ageint}funcinit(){ pool=sync.Pool{New:func()any{ returnnew(person) }}}funcmain(){ p:=pool.Get().(*person) p......
  • Golang实现TCP端口扫描器
    简易版packagemainfuncworker(ports,resultschanint){forp:=rangeports{address:=fmt.Sprintf("bilibili.com:%d",p)conn,err:=net.Dial("tcp",address)iferr!=nil{result<-0conti......
  • GoLang 版本号大小对比
    go版本号大小对比,比如:1.0.0 <1.0.1   思路:按照 .(点符号)分割,分割后将其转成整型,然后再就行对比。//版本号对比使用【版本号=第一位*10000+第二位*100+第三位】//一般版本号是3位,如果大于三位就需要在此进行扩展funcFormatAppVersion(versionNostring)int{......
  • Golang实现代理TCP客户端
    目标网站xxx.com,代理服务器xxxproxy.com,通过代理服务器实现流量转发。packagemainfunchandle(srcnet.Conn){dst,err:=net.Dial("tcp","xxx.com:80")iferr!=nil{log.Fatalln("Unabletoconnectoutunreachablehost")}deferd......
  • 基于client-go实现pod 交互式terminal
    基于client-go实现pod交互式terminal后端实现逻辑(golang)packagemainimport( "errors" "fmt" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" corev1"k8s.io/api/core/v1" "k8s.io/client-go/kubernet......
  • 在命令行中直接使用密码 ssh 登录
    在命令行中直接使用密码ssh登录问题需要在shell脚本中ssh登录远程服务器,上传下载文件,但由于ssh直接使用TTY访问,需要确保密码是用户键盘输入的,每次都需要输入密码,又没有添加免密登录,所以需要在脚本中直接使用密码执行操作。解决方案使用sshpass库,sshpass在专门的......
  • 基于django+ansible+webssh运维自动化管理系统
    基于django+ansible+webssh运维自动化管理系统 前言最初开发这个基于Djangoansible运维自动化管理系统的想法其实从大学时候就已经有了,但是苦于技术原因和没有线上环境原因一直没有开发,现在有了这个技术和环境之后开始着手开发了这个项目,项目难点在于你要理解如何设计数据库,......
  • 关于ssh密码忘记的处理
    01.任意文件位置运行 GitBashHere02.切换到ssh目录:  cd~/.ssh03.查看ssh内容:如果之前已经生成过ssh公钥,则存在三个文件04.删除所有SSH 相关内容: rm-rf~/.ssh/*此时再用 ls指令,已经查不到任何内容了05.创建新的ssh:  ssh-keygen-t......
  • 解决sshd登陆密码错误问题
    一、背景   参考https://blog.csdn.net/qq_43574160/article/details/124870387,在海思开发移植ssh,在确认了root密码正确后,通过终端ssd始终提示登陆密码错误;二、解决过程   1、网上查找类似解决方法,修改默认的配置文件/usr/local/etc/ssh_config,下图为默认配置 ......