

Django实现WebSSH操作物理机或虚拟机
source link: https://blog.ops-coffee.cn/s/a3ejjvttuujzwyk21ntbqq
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Django实现WebSSH操作物理机或虚拟机
我想用它替换掉xshell、crt之类的工具
WebSSH操作物理机或虚拟机
Django实现WebSSH操作Kubernetes Pod文章发布后,有小伙伴说咖啡哥,我们现在还没有用上Kubernetes,但我想通过浏览器连接我们的物理机和虚拟机该怎么办?
这就比较简单了,既然我们已经实现了浏览器操作Kubernetes的Pod,那么想想操作Pod和物理机虚拟机有什么区别呢?
整个数据流是一点没变:用户打开浏览器--》浏览器发送websocket请求给Django建立长连接--》Django与要操作的服务器建立SSH通道,实时的将收到的用户数据发送给SSH后的主机,并将主机执行的结果数据返回给浏览器
唯一不一样的地方就是Django与要操作的服务器建立SSH通道的方式,在Kubernetes中是通过Kubernetes提供的API建立的Stream流,而操作物理机或者虚拟机的时候我们可以使用Paramiko
模块来建立SSH长连接隧道,Paramiko
模块建立SSH长连接通道的方法如下:
# 实例化SSHClient
ssh = paramiko.SSHClient()
# 当远程服务器没有本地主机的密钥时自动添加到本地,这样不用在建立连接的时候输入yes或no进行确认
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接SSH服务器,这里以账号密码的方式进行认证,也可以用key
ssh.connect(hostname=host, port=port, username=username, password=password, timeout=8)
# 打开ssh通道,建立长连接
transport = ssh.get_transport()
self.ssh_channel = transport.open_session()
# 获取ssh通道,并设置term和终端大小
self.ssh_channel.get_pty(term=term, width=cols, height=rows)
# 激活终端,这样就可以正常登陆了
self.ssh_channel.invoke_shell()
连接建立,可以通过如下方法给SSH通道发送数据
self.ssh_channel.send(data)
当然SSH返回的数据也可以通过如下方法持续的输出给Websocket
while not self.ssh_channel.exit_status_ready():
# SSH返回的数据需要转码为utf-8,否则json序列化会失败
data = self.ssh_channel.recv(1024).decode('utf-8','ignore')
if len(data) != 0:
message = {'flag': 'success', 'message': data}
self.websocket.send(json.dumps(message))
else:
break
有了这些信息,结合Django实现WebSSH操作Kubernetes Pod的文章,实现WebSSH浏览器操作物理机或者虚拟机就不算困难了,完整的Consumer代码如下:
import io
import json
import paramiko
from threading import Thread
from channels.generic.websocket import WebsocketConsumer
from cmdb.backends.sshargs import args
class SSHBridge(object):
def __init__(self, websocket):
self.websocket = websocket
def connect(self, host, port, username, authtype, password=None, pkey=None, term='xterm', cols=80, rows=24):
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try:
if authtype == 2:
pkey = paramiko.RSAKey.from_private_key(io.StringIO(pkey))
ssh.connect(username=username, hostname=host, port=port, pkey=pkey, timeout=8)
else:
ssh.connect(hostname=host, port=port, username=username, password=password, timeout=8)
except Exception as e:
message = json.dumps({'flag': 'error', 'message': str(e)})
self.websocket.send(message)
return False
# 打开一个ssh通道并建立连接
transport = ssh.get_transport()
self.ssh_channel = transport.open_session()
self.ssh_channel.get_pty(term=term, width=cols, height=rows)
self.ssh_channel.invoke_shell()
# 连接建立一次,之后交互数据不会再进入该方法
for i in range(2):
recv = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
message = json.dumps({'flag': 'success', 'message': recv})
self.websocket.send(message)
def close(self):
try:
self.websocket.close()
self.ssh_channel.close()
except BaseException as e:
pass
def _ws_to_ssh(self, data):
try:
self.ssh_channel.send(data)
except OSError as e:
self.close()
def _ssh_to_ws(self):
try:
while not self.ssh_channel.exit_status_ready():
data = self.ssh_channel.recv(1024).decode('utf-8', 'ignore')
if len(data) != 0:
message = {'flag': 'success', 'message': data}
self.websocket.send(json.dumps(message))
else:
break
except Exception as e:
message = {'flag': 'error', 'message': str(e)}
self.websocket.send(json.dumps(message))
self.close()
def shell(self, data):
Thread(target=self._ws_to_ssh, args=(data,)).start()
Thread(target=self._ssh_to_ws).start()
class SSHConsumer(WebsocketConsumer):
def connect(self):
self.pk = self.scope['url_route']['kwargs'].get('id')
self.query = self.scope.get('query_string')
self.user = self.scope['user']
self.accept()
# ssh_connect_args为SSH连接需要的参数
ssh_connect_args = args(self.pk, self.user, self.query)
self.ssh = SSHBridge(websocket=self)
self.ssh.connect(**ssh_connect_args)
def disconnect(self, close_code):
self.ssh.close()
def receive(self, text_data=None):
text_data = json.loads(text_data)
self.ssh.shell(data=text_data.get('data', ''))
动态调整终端窗口大小
看了Kubernetes WebSSH终端窗口自适应Resize文章,小伙伴又说了,你这只能在连接建立时确定终端窗口的大小,如果我中途调整了浏览器的大小,显示就乱了,这该怎么办?
不要着急,接下来就让我们看看怎么让终端窗口随着浏览器大小的调整而改变,上边的文章中已经说过,终端窗口的大小需要浏览器和后端返回的Terminal大小保持一致,单单调整页面窗口大小或者后端返回的Terminal窗口大小都是不行的,那么从这两个方向来说明该如何动态调整窗口的大小
首先Paramiko
模块建立的SSH通道可以通过resize_pty
来动态改变返回Terminal窗口的大小,使用方法如下:
def resize_pty(self, cols, rows):
self.ssh_channel.resize_pty(width=cols, height=rows)
然后Django的Channels每次接收到前端发过来的数据时,判断一下窗口是否有变化,如果有变化则调用上边的方法动态改变Terminal输出窗口的大小
我在实现时会给传过来的数据加个flag,如果flag是resize,则调用resize_pty的方法动态调整窗口大小,否则就正常调用执行命令的方法,代码如下:
def receive(self, text_data=None):
text_data = json.loads(text_data)
if text_data.get('flag') == 'resize':
self.ssh.resize_pty(cols=text_data['cols'], rows=text_data['rows'])
else:
self.ssh.shell(data=text_data.get('data', ''))
后端都搞定了,那么来看看前端如何处理吧
首先有一个terminal_size的方法根据浏览器窗口大小除以每个字符所占用的大小计算出cols和rows的值,无论是xterm.js还是Paramiko都是根据这两个值来调整窗口大小的
function terminal_size() {
return {
cols: Math.floor($('#terminal').width() / 9),
rows: Math.floor($(window).height() / 17),
}
}
然后通过$(window).resize()
来检测浏览器窗口的变化,一旦发生变化,则发送一个带resize标记的数据给Django,同时传递的数据还有新的cols和rows
// terminal resize
$(window).resize(function () {
let cols = terminal_size().cols;
let rows = terminal_size().rows;
send_data = JSON.stringify({
'flag': 'resize',
'cols': cols,
'rows': rows
});
socket.send(send_data);
term.resize(cols, rows)
})
最后通过term.resize
来调整xterm渲染的窗口的大小
这样一个完整的动态调整窗口大小的方案就完成了
演示与源码
我写了个简单的Demo来实现上边的功能,Demo写完发现还挺好用,我就扩展了一下添加了内网的物理机和虚拟机,历史原因,有些是账号密码认证,有些是密钥认证,我都给兼容了一下,最终实现的效果如上图所示
项目里边要记录主机的密码,为了安全这个密码是通过RSA加密存放在数据库的,每次使用的时候进行解密,加解密的实现,可参考这篇文章 Django开发密码管理表实例【附源码】
最后,如果你对这个简单的小玩意感兴趣,想要自己实现,却遇到了一些问题,可以通过公众号后台加我微信获取源码
能看到这里一定是真爱,关注一下吧

Recommend
-
69
新建一个django程序,本文为chain。以下仅为简单例子,实际应用可根据自己平台情况进行修改。打开首页后,需要输入1,后台去登录主机,然后返回登录结果。正常项目可以post主机和登录账户,进行权限判断,然后去后台读取账户密码,进行登录。djang后台需要安装以下...
-
37
目前裸机(物理机)、虚拟机、容器是云计算提供计算服务的三种主流形式。最近有人问,如何判断一个虚拟shell环境到底是物理机、虚拟机还是容器? 更进一步,如果是物理机,这个物理机厂商是什么,虚拟机到底是KVM还是XEN,容器是Do...
-
201
README.md WebSSH
-
6
V2EX › 程序员 物理机&&虚拟机 抓包问题 AJDX3906 · 13 小时 56 分钟前...
-
7
堡垒机的核心武器:WebSSH录像实现 WebSSH终端录像的实现终于来了 前边写了两篇文章『Asciinema:你的所有操作都将被录制』和
-
5
Django实现WebSSH操作Kubernetes Pod 优秀的系统都是根据反馈逐渐完善出来的 上篇文章介绍了我们为了应对安全和多分支频繁测试的问题而开发了一套Alodi系统,Alodi可以通过一个按钮快速构建一套测试环境,...
-
7
设置VMware workstation 虚拟机连物理机网络上网方法 精选 原创 一、NAT(物理机和虚拟机在...
-
9
使用VMware Converter Standalone P2V(物理机转换虚拟机) 环境说明: 1、P2V软件:VMware-converter-en-6.3.0-20575345 下载地址:vCenter Converter: P2V Virtu...
-
5
用go+xtermjs实现的一个webssh的demo rushui · 大约10小时之前 · 178 次点...
-
3
V2EX › 程序员 从 ubuntu22.04 虚拟机迁移到 fedora38 物理机, go tcp server 压测表现提升了 81% !
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK