上一篇: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代码如下

<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终端窗口自适应(二)的更多相关文章

  1. 使用OpenSSH远程管理Linux服务器

    一.使用OpenSSH远程管理Linux服务器 sshd是OpenSSH的服务器端守护进程,与之对应的Windows下客户端软件有SecureCRT/Xshell/PuTTY等. OpenSSH一般为 ...

  2. 在linux终端远程登陆linux服务器

    在linux终端远程登陆linux服务器   原来在Linux终端远程登陆linux服务器是那么的容易,如果的服务器用户名是abc(也可以是root),只需要在终端输入: 然后电脑会提示输入密码就登录 ...

  3. widows终端远程连接Linux服务器

    一.前言 为什么不是远程连接Linux服务器? 因为我不会,远程连接window我就用电脑自带的“远程桌面连接”. 以下所述都是在CentOS操作系统下的. 服务器刚换成Linux的时候很迷茫,感觉无 ...

  4. Windows 上的 SSH?使用 PowerShell Remoting 远程管理 Windows 服务器

    作者:陈计节 个人博客:https://blog.jijiechen.com/post/powershell-remoting/ 在 Linux/Unix 世界里 SSH 是个好东西,SSH 是 Se ...

  5. widows本地-xshell实现远程连接linux服务器图形界面

    本地环境远程连接linux图形界面,常用的实现工具有,VNC.Puty.Xshell等,这里我们用的xshell manager: Xmanager简介:Xmanager是一个运行于 Windows平 ...

  6. mac远程连接linux 服务器桌面by VNC

    为了远程使用Linux服务器,折腾了一个下午.最终看来还是用vnc最简单了. 实验室有两台强劲的Linux服务器用来做研究.之前我一直都是用ssh登到服务器上去码代码,反应速度很快,感觉很不错.但是因 ...

  7. 推荐两款远程管理Linux工具(基于Windows系统)

    推荐两款远程管理Linux工具(基于Windows系统) 1.Xshell 百度百科:Xshell 是一个强大的安全终端模拟软件,它支持SSH1, SSH2, 以及Microsoft Windows ...

  8. Microsoft Azure Web Sites应用与实践【2】—— 通过本地IIS 远程管理Microsoft Azure Web Site

    Microsoft Azure Web Sites应用与实践 系列: [1]—— 打造你的第一个Microsoft Azure Website [2]—— 通过本地IIS 远程管理Microsoft ...

  9. 如何使用图形界面Webmin管理linux服务器

    出处:http://linux.cn/thread/11992/1/1/ 如何使用图形界面Webmin管理linux服务器 一台典型的linux服务器运行命令行环境中,并已经包括了一些用于安装和配置各 ...

  10. SecureCRT远程ssh linux服务器,利用X11本地图形化wireshark抓包,

    平时学习抓包,使用wireshark很方便,直接图形化界面抓包,近期项目中遇到了需要通过SecureCRT ssh到linux服务器上面,还要在抓包, 1.简单使用网络数据包截获分析工具tcpdump ...

随机推荐

  1. 搭建CTF动态靶场

    前言 本文借鉴文章:https://www.yuque.com/dengfenglai-esbap/kb/mc4k41?#xOxNG 在此基础上修改了一点(照着原来的做没成功),感谢这位师傅给的资源. ...

  2. LangChain vs Semantic Kernel

    每当向他人介绍 Semantic Kernel, 会得到的第一个问题就是 Semantic Kernel 类似于LangChain吗,或者是c# 版本的LangChain吗? 为了全面而不想重复的回答 ...

  3. ChatGPT在工业领域的研究与应用探索-数据与工况认知

    1.      ChatGPT发展现状... 2 2.      ChatGPT如何与工业相结合... 2 3.      ChatGPT在工业领域的研究与应用... 3 1.   ChatGPT发展 ...

  4. 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟

    目录 沁恒 CH32V208(一): CH32V208WBU6 评估板上手报告和Win10环境配置 沁恒 CH32V208(二): CH32V208的储存结构, 启动模式和时钟 CH32V 存储容量命 ...

  5. selenium 执行js脚本

    使用 selenium 直接在当前页面中进行js交互 使用selenium 执行 Js 脚本 要使用 js 首先要知道 js 怎么用,现在举个简单得例子,就用12306举例子, 它的首页日期选择框是只 ...

  6. 2022-06-17:给定一个数组arr,含有n个数字,可能有正、有负、有0, 给定一个正数k。 返回所有子序列中,累加和最大的前k个子序列累加和。 假设K不大,怎么算最快? 来自Amazon。

    2022-06-17:给定一个数组arr,含有n个数字,可能有正.有负.有0, 给定一个正数k. 返回所有子序列中,累加和最大的前k个子序列累加和. 假设K不大,怎么算最快? 来自Amazon. 答案 ...

  7. 2022-05-03:Alice 和 Bob 再次设计了一款新的石子游戏。现有一行 n 个石子,每个石子都有一个关联的数字表示它的价值。给你一个整数数组 stones ,其中 stones[i] 是第

    2022-05-03:Alice 和 Bob 再次设计了一款新的石子游戏.现有一行 n 个石子,每个石子都有一个关联的数字表示它的价值.给你一个整数数组 stones ,其中 stones[i] 是第 ...

  8. 2021-12-09:二叉树展开为链表。 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左

    2021-12-09:二叉树展开为链表. 给你二叉树的根结点 root ,请你将它展开为一个单链表: 展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左 ...

  9. vue全家桶进阶之路20:ECMAScript脚本语言规范

    ECMAScript(简称 ES)是一种由 Ecma 国际组织定义的脚本语言标准,它定义了 JavaScript 语言的基本规范和特性.JavaScript 是一种基于 ECMAScript 标准的编 ...

  10. drf序列化器之反序列化的数据验证

    模型层 from django.db import models # Create your models here. class Manufacturer(models.Model): ## 厂商 ...